rainforest-cli 1.10.2 → 1.10.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|