rainforest-cli 1.10.2 → 1.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/rainforest_cli.rb +3 -0
- data/lib/rainforest_cli/commands.rb +2 -0
- data/lib/rainforest_cli/csv_importer.rb +8 -3
- data/lib/rainforest_cli/deleter.rb +1 -0
- data/lib/rainforest_cli/http_client.rb +43 -57
- data/lib/rainforest_cli/reporter.rb +0 -10
- data/lib/rainforest_cli/runner.rb +8 -14
- data/lib/rainforest_cli/version.rb +1 -1
- data/spec/rainforest_cli/csv_importer_spec.rb +1 -1
- data/spec/rainforest_cli/http_client_spec.rb +47 -39
- data/spec/rainforest_cli/runner_spec.rb +0 -12
- data/spec/rainforest_cli_spec.rb +15 -10
- data/spec/spec_helper.rb +0 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 404d70dc620de15e7e6eeb220d60a1809f722ee1
|
4
|
+
data.tar.gz: 9a23f6f532cb6cf9eaf6139868a876d020453392
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34ce70554f9807d0e2db16dd7c6eb58f5a219da7198b3c4db64dfa97ebd745585303ceac5c6f4bf501e45c2eb1bc0274fc023d73bd0642f35216ca65f5c3e38b
|
7
|
+
data.tar.gz: 10b6f5678629a9b9516ca2e6837e04569ffb79b61a35c97e2602073fff16f4f83372fb37debb9b3b82b30182846041ff88d27c1e2b184f7409409bc4424aee68
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Rainforest CLI Changelog
|
2
2
|
|
3
|
+
## 1.10.3 - 21st October 2016
|
4
|
+
- Fix bug with accidental removal of 3 commands. (d9ebbd702ba1b1b64b9a1a222c301315b89d743e, @curtis-rainforestqa)
|
5
|
+
- Add better tolerance for server errors. (b4180332ca13fec0972c3d65dbce00242907ca38, @epaulet)
|
6
|
+
|
3
7
|
## 1.10.2 - 11th October 2016
|
4
8
|
- Limit CSV batch upload size to chunks of 20 at a time for more reliability.
|
5
9
|
(abfed07df5635eb88029ee0b6cf8eea3a538fff6, @epaulet)
|
data/lib/rainforest_cli.rb
CHANGED
@@ -33,6 +33,9 @@ module RainforestCli
|
|
33
33
|
c.add('export', 'Export your remote Rainforest tests to RFML') { Exporter.new(options).export }
|
34
34
|
c.add('csv-upload', 'Upload a new tabular variable from a CSV file') { CSVImporter.new(options).import }
|
35
35
|
c.add('report', 'Create a JUnit report from your run results') { Reporter.new(options).report }
|
36
|
+
c.add('sites', 'Lists the available sites') { Resources.new(options).sites }
|
37
|
+
c.add('folders', 'Lists the available folders') { Resources.new(options).folders }
|
38
|
+
c.add('browsers', 'Lists the available browsers') { Resources.new(options).browsers }
|
36
39
|
end
|
37
40
|
|
38
41
|
@http_client = HttpClient.new(token: options.token)
|
@@ -40,8 +40,12 @@ module RainforestCli
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
response = http_client.post
|
43
|
+
puts 'Creating new tabular variable'
|
44
|
+
response = http_client.post(
|
45
|
+
'/generators',
|
46
|
+
{ name: @generator_name, description: @generator_name, columns: columns },
|
47
|
+
{ retries_on_failures: true },
|
48
|
+
)
|
45
49
|
raise "Error creating tabular variable: #{response['error']}" if response['error']
|
46
50
|
puts "\t[OK]"
|
47
51
|
|
@@ -57,7 +61,8 @@ module RainforestCli
|
|
57
61
|
response = http_client.post(
|
58
62
|
"/generators/#{generator_id}/rows/batch",
|
59
63
|
{ data: data_slice },
|
60
|
-
retries_on_failures: true
|
64
|
+
{ retries_on_failures: true },
|
65
|
+
)
|
61
66
|
# NOTE: Response for this endpoint will usually be an array representing all the rows created
|
62
67
|
raise response['error'] if response.is_a?(Hash) && response['error']
|
63
68
|
p.increment
|
@@ -13,81 +13,58 @@ module RainforestCli
|
|
13
13
|
@token = options.fetch(:token)
|
14
14
|
end
|
15
15
|
|
16
|
-
def delete(
|
17
|
-
|
18
|
-
body: body,
|
19
|
-
headers: headers,
|
20
|
-
verify: false,
|
21
|
-
}
|
22
|
-
|
23
|
-
JSON.parse(response.body)
|
16
|
+
def delete(path, body = {}, options = {})
|
17
|
+
request(:delete, path, body, options)
|
24
18
|
end
|
25
19
|
|
26
|
-
def post(
|
27
|
-
|
28
|
-
response = HTTParty.post make_url(url), {
|
29
|
-
body: body,
|
30
|
-
headers: headers,
|
31
|
-
verify: false,
|
32
|
-
}
|
33
|
-
|
34
|
-
return JSON.parse(response.body)
|
35
|
-
end
|
20
|
+
def post(path, body = {}, options = {})
|
21
|
+
request(:post, path, body, options)
|
36
22
|
end
|
37
23
|
|
38
|
-
def get(
|
39
|
-
|
40
|
-
response = HTTParty.get make_url(url), {
|
41
|
-
body: body,
|
42
|
-
headers: headers,
|
43
|
-
verify: false,
|
44
|
-
}
|
45
|
-
|
46
|
-
if response.code == 200
|
47
|
-
return JSON.parse(response.body)
|
48
|
-
else
|
49
|
-
RainforestCli.logger.warn("Status Code: #{response.code}, #{response.body}")
|
50
|
-
return nil
|
51
|
-
end
|
52
|
-
end
|
24
|
+
def get(path, body = {}, options = {})
|
25
|
+
request(:get, path, body, options)
|
53
26
|
end
|
54
27
|
|
55
|
-
def
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
28
|
+
def request(method, path, body, options)
|
29
|
+
url = File.join(API_URL, path)
|
60
30
|
|
61
|
-
def wrap_exceptions(retries_on_failures)
|
62
|
-
@retry_delay = 0
|
63
|
-
@waiting_on_retries = false
|
64
31
|
loop do
|
65
32
|
begin
|
66
|
-
|
67
|
-
|
68
|
-
|
33
|
+
response = Http::Exceptions.wrap_exception do
|
34
|
+
HTTParty.send(method, url, { body: body, headers: headers, verify: false })
|
35
|
+
end
|
36
|
+
|
37
|
+
if response.code.between?(200, 299)
|
38
|
+
return JSON.parse(response.body)
|
39
|
+
elsif options[:retries_on_failures] && response.code >= 500
|
40
|
+
delay = retry_delay
|
41
|
+
logger.warn "HTTP request was unsuccessful. URL: #{url}. Status: #{response.code}"
|
42
|
+
logger.warn "Retrying again in #{delay} seconds..."
|
43
|
+
Kernel.sleep delay
|
69
44
|
else
|
70
|
-
|
71
|
-
|
45
|
+
logger.fatal "Non 200 code received for request to #{url}"
|
46
|
+
logger.fatal "Server response: #{response.body}"
|
47
|
+
exit 1
|
72
48
|
end
|
73
49
|
rescue Http::Exceptions::HttpException, Timeout::Error => e
|
74
|
-
raise e unless retries_on_failures
|
50
|
+
raise e unless options[:retries_on_failures]
|
75
51
|
|
76
|
-
|
77
|
-
|
78
|
-
|
52
|
+
delay = retry_delay
|
53
|
+
logger.warn 'Exception Encountered while trying to contact Rainforest API:'
|
54
|
+
logger.warn "\t\t#{e.message}"
|
55
|
+
logger.warn "Retrying again in #{delay} seconds..."
|
79
56
|
|
80
|
-
|
81
|
-
RainforestCli.logger.warn "\t\t#{e.message}"
|
82
|
-
RainforestCli.logger.warn "Retrying again in #{@retry_delay} seconds..."
|
83
|
-
|
84
|
-
Kernel.sleep @retry_delay
|
85
|
-
@waiting_on_retries = false
|
86
|
-
end
|
57
|
+
Kernel.sleep delay
|
87
58
|
end
|
88
59
|
end
|
89
60
|
end
|
90
61
|
|
62
|
+
def api_token_set?
|
63
|
+
!@token.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
91
68
|
def make_url(url)
|
92
69
|
File.join(API_URL, url)
|
93
70
|
end
|
@@ -98,5 +75,14 @@ module RainforestCli
|
|
98
75
|
'User-Agent' => "Rainforest-cli-#{RainforestCli::VERSION}",
|
99
76
|
}
|
100
77
|
end
|
78
|
+
|
79
|
+
def retry_delay
|
80
|
+
# make retry delay random to desynchronize multiple threads
|
81
|
+
RETRY_INTERVAL + rand(RETRY_INTERVAL)
|
82
|
+
end
|
83
|
+
|
84
|
+
def logger
|
85
|
+
RainforestCli.logger
|
86
|
+
end
|
101
87
|
end
|
102
88
|
end
|
@@ -22,11 +22,6 @@ module RainforestCli
|
|
22
22
|
|
23
23
|
run = client.get("/runs/#{@run_id}.json")
|
24
24
|
|
25
|
-
if run == nil
|
26
|
-
logger.fatal "Non 200 code recieved"
|
27
|
-
exit 1
|
28
|
-
end
|
29
|
-
|
30
25
|
if run['error']
|
31
26
|
logger.fatal "Error retrieving results for your run: #{run['error']}"
|
32
27
|
exit 1
|
@@ -35,11 +30,6 @@ module RainforestCli
|
|
35
30
|
if run.has_key?('total_tests') and run['total_tests'] != 0
|
36
31
|
tests = client.get("/runs/#{@run_id}/tests.json?page_size=#{run['total_tests']}")
|
37
32
|
|
38
|
-
if tests == nil
|
39
|
-
logger.fatal "Non 200 code recieved"
|
40
|
-
exit 1
|
41
|
-
end
|
42
|
-
|
43
33
|
if tests.kind_of?(Hash) and tests['error'] # if this had worked tests would be an array
|
44
34
|
logger.fatal "Error retrieving test details for your run: #{tests['error']}"
|
45
35
|
exit 1
|
@@ -54,16 +54,14 @@ module RainforestCli
|
|
54
54
|
running = true
|
55
55
|
while running
|
56
56
|
response = client.get("/runs/#{run_id}", {}, retries_on_failures: true)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
running = false
|
66
|
-
end
|
57
|
+
state_details = response.fetch('state_details')
|
58
|
+
unless state_details.fetch('is_final_state')
|
59
|
+
state, current_progress = response.values_at('state', 'current_progress')
|
60
|
+
logger.info "Run #{run_id} is #{state} and is #{current_progress['percent']}% complete"
|
61
|
+
running = false if response['result'] == 'failed' && options.failfast?
|
62
|
+
else
|
63
|
+
logger.info "Run #{run_id} is now #{response["state"]} and has #{response["result"]}"
|
64
|
+
running = false
|
67
65
|
end
|
68
66
|
Kernel.sleep 5 if running
|
69
67
|
end
|
@@ -184,10 +182,6 @@ module RainforestCli
|
|
184
182
|
end
|
185
183
|
|
186
184
|
url = client.get('/uploads', {}, retries_on_failures: true)
|
187
|
-
unless url
|
188
|
-
logger.fatal "Failed to upload file #{app_source_url}. Please, check your API token."
|
189
|
-
exit 1
|
190
|
-
end
|
191
185
|
data = File.read(app_source_url)
|
192
186
|
logger.info 'Uploading app source file, this operation may take few minutes...'
|
193
187
|
response_code = upload_file(url, data)
|
@@ -1,79 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
describe RainforestCli::HttpClient do
|
3
|
-
|
3
|
+
let(:path) { '/my/path' }
|
4
|
+
let(:success_response) { instance_double('HTTParty::Response', code: 200, body: {'success'=>'true'}.to_json) }
|
4
5
|
|
5
|
-
|
6
|
-
describe
|
7
|
-
|
6
|
+
[:get, :post, :delete].each do |m|
|
7
|
+
describe "##{m}" do
|
8
|
+
subject { described_class.new({ token: 'foo' }) }
|
9
|
+
|
10
|
+
it 'makes the correct type of request' do
|
11
|
+
expect(HTTParty).to receive(m).and_return(success_response)
|
12
|
+
subject.public_send(m, path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
8
16
|
|
17
|
+
describe '#request' do
|
18
|
+
let(:method) { :get }
|
19
|
+
let(:body) { { foo: :bar } }
|
20
|
+
let(:options) { {} }
|
21
|
+
subject { described_class.new({ token: 'foo' }).request(method, path, body, options) }
|
22
|
+
|
23
|
+
describe 'maximum tolerated exceptions' do
|
9
24
|
before do
|
10
|
-
allow(HTTParty).to receive(
|
25
|
+
allow(HTTParty).to receive(method).and_raise(SocketError)
|
11
26
|
end
|
12
27
|
|
13
28
|
context 'retries_on_failures omitted' do
|
14
29
|
it 'raises the error on the first exception' do
|
15
|
-
expect(HTTParty).to receive(
|
16
|
-
expect { subject
|
30
|
+
expect(HTTParty).to receive(method).once
|
31
|
+
expect { subject }.to raise_error(Http::Exceptions::HttpException)
|
17
32
|
end
|
18
33
|
end
|
19
34
|
|
20
35
|
context 'retries_on_failures == false' do
|
36
|
+
let(:options) { { retries_on_failures: false } }
|
37
|
+
|
21
38
|
it 'raises the error on the first exception' do
|
22
|
-
expect(HTTParty).to receive(
|
23
|
-
expect { subject
|
39
|
+
expect(HTTParty).to receive(method).once
|
40
|
+
expect { subject }.to raise_error(Http::Exceptions::HttpException)
|
24
41
|
end
|
25
42
|
end
|
26
43
|
|
27
44
|
context 'retries_on_failures == true' do
|
45
|
+
let(:options) { { retries_on_failures: true } }
|
28
46
|
let(:response) { instance_double('HTTParty::Response', code: 200, body: {foo: :bar}.to_json) }
|
29
47
|
let(:delay_interval) { described_class::RETRY_INTERVAL }
|
30
|
-
subject { described_class.new({ token: 'foo' }).get(url, {}, retries_on_failures: true) }
|
31
48
|
|
32
49
|
it 'it sleeps after failures before a retry' do
|
33
|
-
expect(HTTParty).to receive(
|
34
|
-
expect(HTTParty).to receive(
|
35
|
-
expect(Kernel).to receive(:sleep)
|
50
|
+
expect(HTTParty).to receive(method).and_raise(SocketError).once.ordered
|
51
|
+
expect(HTTParty).to receive(method).and_return(response).ordered
|
52
|
+
expect(Kernel).to receive(:sleep)
|
36
53
|
expect { subject }.to_not raise_error
|
37
54
|
end
|
38
55
|
|
39
56
|
it 'sleeps for longer periods with repeated exceptions' do
|
40
|
-
expect(HTTParty).to receive(
|
41
|
-
expect(HTTParty).to receive(
|
42
|
-
expect(Kernel).to receive(:sleep).
|
43
|
-
expect(Kernel).to receive(:sleep).with(delay_interval * 2).once
|
44
|
-
expect(Kernel).to receive(:sleep).with(delay_interval * 3).once
|
57
|
+
expect(HTTParty).to receive(method).and_raise(SocketError).exactly(3).times.ordered
|
58
|
+
expect(HTTParty).to receive(method).and_return(response).ordered
|
59
|
+
expect(Kernel).to receive(:sleep).exactly(3).times
|
45
60
|
expect { subject }.to_not raise_error
|
46
61
|
end
|
47
62
|
|
48
63
|
it 'returns the response upon success' do
|
49
|
-
expect(HTTParty).to receive(
|
50
|
-
expect(HTTParty).to receive(
|
51
|
-
expect(Kernel).to receive(:sleep).
|
64
|
+
expect(HTTParty).to receive(method).and_raise(SocketError).once.ordered
|
65
|
+
expect(HTTParty).to receive(method).and_return(response).ordered
|
66
|
+
expect(Kernel).to receive(:sleep).once
|
52
67
|
expect(subject).to eq(JSON.parse(response.body))
|
53
68
|
end
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
57
|
-
describe '
|
58
|
-
|
59
|
-
|
60
|
-
let(:response) { instance_double('HTTParty::Response', code: 404, body: {'error'=>'some error', 'type'=>'some type'}.to_json) }
|
61
|
-
subject { described_class.new({ token: 'foo' }).get(url, {}) }
|
62
|
-
|
63
|
-
before do
|
64
|
-
allow(HTTParty).to receive(:get).and_raise(SocketError)
|
65
|
-
end
|
72
|
+
describe 'unsuccessful status codes' do
|
73
|
+
let(:url) { 'http://some.url.com' }
|
74
|
+
let(:bad_response) { instance_double('HTTParty::Response', code: 404, body: {'error'=>'some error', 'type'=>'some type'}.to_json) }
|
66
75
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
expect(subject)
|
71
|
-
end
|
76
|
+
before do
|
77
|
+
allow(HTTParty).to receive(method).and_raise(SocketError)
|
78
|
+
end
|
72
79
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
80
|
+
it 'gets an error 404 and prints the error and exits' do
|
81
|
+
expect(HTTParty).to receive(method).and_return(bad_response)
|
82
|
+
expect_any_instance_of(Logger).to receive(:fatal).with(a_string_including('Non 200 code received'))
|
83
|
+
expect_any_instance_of(Logger).to receive(:fatal).with(a_string_including(bad_response.body.to_s))
|
84
|
+
expect { subject }.to raise_error(SystemExit)
|
77
85
|
end
|
78
86
|
end
|
79
87
|
end
|
@@ -86,18 +86,6 @@ describe RainforestCli::Runner do
|
|
86
86
|
}
|
87
87
|
end
|
88
88
|
|
89
|
-
it 'errors out and exits if valid file but invalid token' do
|
90
|
-
File.should_receive(:exist?).with('fobar.ipa') { true }
|
91
|
-
subject.client.should_receive(:get).with('/uploads', {}, retries_on_failures: true) { nil }
|
92
|
-
expect_any_instance_of(Logger).to receive(:fatal).with(
|
93
|
-
'Failed to upload file fobar.ipa. Please, check your API token.')
|
94
|
-
expect do
|
95
|
-
subject.upload_app('fobar.ipa')
|
96
|
-
end.to raise_error(SystemExit) { |error|
|
97
|
-
expect(error.status).to eq 1
|
98
|
-
}
|
99
|
-
end
|
100
|
-
|
101
89
|
it 'errors out and exits if was not possible to upload the file' do
|
102
90
|
File.should_receive(:exist?).with('fobar.ipa') { true }
|
103
91
|
File.should_receive(:read).with('fobar.ipa') { 'File data' }
|
data/spec/rainforest_cli_spec.rb
CHANGED
@@ -210,19 +210,24 @@ describe RainforestCli do
|
|
210
210
|
expect(described_class.start(valid_args)).to eq(true)
|
211
211
|
end
|
212
212
|
end
|
213
|
+
end
|
213
214
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
expect_any_instance_of(http_client).to receive(:get)
|
219
|
-
expect_any_instance_of(http_client).to receive(:get).twice.and_return(ok_progress)
|
220
|
-
expect_any_instance_of(http_client).to receive(:get).and_return(complete_response)
|
221
|
-
end
|
215
|
+
context 'commands' do
|
216
|
+
let(:valid_args) { %w(some args) }
|
217
|
+
let(:command) { double(:command, call: true) }
|
218
|
+
let(:option_parser) { double(:option_parser, token: '123abc', validate!: true, command: 'some-cmd') }
|
222
219
|
|
223
|
-
|
224
|
-
|
220
|
+
before do
|
221
|
+
allow(RainforestCli::OptionParser).to receive(:new) { option_parser }
|
222
|
+
allow(RainforestCli::Commands).to receive(:new).and_yield(command).and_return(command)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'we register all currently supported commands' do
|
226
|
+
RainforestCli::Commands::SUPPORTED_COMMANDS.each do |supported_command|
|
227
|
+
expect(command).to receive(:add).with(supported_command, kind_of(String))
|
225
228
|
end
|
229
|
+
|
230
|
+
described_class.start(valid_args)
|
226
231
|
end
|
227
232
|
end
|
228
233
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -15,9 +15,6 @@ RSpec.configure do |config|
|
|
15
15
|
config.order = 'random'
|
16
16
|
|
17
17
|
config.before do
|
18
|
-
# suppress output in terminal
|
19
|
-
allow_any_instance_of(Object).to receive(:puts)
|
20
|
-
|
21
18
|
progressbar_mock = double('ProgressBar')
|
22
19
|
allow(ProgressBar).to receive(:create).and_return(progressbar_mock)
|
23
20
|
allow(progressbar_mock).to receive(:increment)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rainforest-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.10.
|
4
|
+
version: 1.10.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Russell Smith
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-10-
|
12
|
+
date: 2016-10-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httparty
|