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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e31fc0fb7e685c2de8fd444e7d5f2a074d19875
4
- data.tar.gz: ad40b1b738892368b8597e779a23c25f621dc5a3
3
+ metadata.gz: 404d70dc620de15e7e6eeb220d60a1809f722ee1
4
+ data.tar.gz: 9a23f6f532cb6cf9eaf6139868a876d020453392
5
5
  SHA512:
6
- metadata.gz: 9ea8b9dc6ad22b570efae9b6f379631130062653a92f982b02660fe9dfc8b60f61a2f7678732a0837faf4fb94f613e5c5bd5d8fdfc03e8ecc250fd299ec46c1f
7
- data.tar.gz: 644af9ed6b8064c8cfcfd17fe7aaeed20d992d073d968bcb73e66e13533c665c3010bcfea52957444a03dde6488622d9691e74fcf6660b229e881463ba217afc
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)
@@ -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)
@@ -6,6 +6,8 @@ module RainforestCli
6
6
 
7
7
  attr_reader :commands
8
8
 
9
+ SUPPORTED_COMMANDS = %w(run new validate upload rm export csv-upload report sites folders browsers)
10
+
9
11
  def initialize
10
12
  @commands = []
11
13
  yield(self) if block_given?
@@ -40,8 +40,12 @@ module RainforestCli
40
40
  end
41
41
  end
42
42
 
43
- print 'Creating new tabular variable'
44
- response = http_client.post '/generators', { name: @generator_name, description: @generator_name, columns: columns }
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
@@ -14,6 +14,7 @@ class RainforestCli::Deleter
14
14
  validate_file_extension
15
15
  delete_remote_test(test_file)
16
16
  delete_local_file(test_file.file_name)
17
+ logger.info 'Test successfully deleted.'
17
18
  end
18
19
 
19
20
  private
@@ -13,81 +13,58 @@ module RainforestCli
13
13
  @token = options.fetch(:token)
14
14
  end
15
15
 
16
- def delete(url, body = {})
17
- response = HTTParty.delete make_url(url), {
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(url, body = {}, options = {})
27
- wrap_exceptions(options[:retries_on_failures]) do
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(url, body = {}, options = {})
39
- wrap_exceptions(options[:retries_on_failures]) do
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 api_token_set?
56
- !@token.nil?
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
- # Suspend tries until wait period is over
67
- if @waiting_on_retries
68
- Kernel.sleep 5
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
- Http::Exceptions.wrap_exception { yield }
71
- break
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
- unless @waiting_on_retries
77
- @waiting_on_retries = true
78
- @retry_delay += RETRY_INTERVAL
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
- RainforestCli.logger.warn 'Exception Encountered while trying to contact Rainforest API:'
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
- if response
58
- state_details = response.fetch('state_details')
59
- unless state_details.fetch('is_final_state')
60
- state, current_progress = response.values_at('state', 'current_progress')
61
- logger.info "Run #{run_id} is #{state} and is #{current_progress['percent']}% complete"
62
- running = false if response['result'] == 'failed' && options.failfast?
63
- else
64
- logger.info "Run #{run_id} is now #{response["state"]} and has #{response["result"]}"
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,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module RainforestCli
3
- VERSION = '1.10.2'
3
+ VERSION = '1.10.3'
4
4
  end
@@ -32,7 +32,7 @@ describe RainforestCli::CSVImporter do
32
32
  name: 'variables',
33
33
  description: 'variables',
34
34
  columns: columns,
35
- })
35
+ }, retries_on_failures: true)
36
36
  .and_return success_response
37
37
 
38
38
  expect(http_client).to receive(:post)
@@ -1,79 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
  describe RainforestCli::HttpClient do
3
- subject { described_class.new({ token: 'foo' }) }
3
+ let(:path) { '/my/path' }
4
+ let(:success_response) { instance_double('HTTParty::Response', code: 200, body: {'success'=>'true'}.to_json) }
4
5
 
5
- describe '#get' do
6
- describe 'maximum tolerated exceptions' do
7
- let(:url) { 'http://some.url.com' }
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(:get).and_raise(SocketError)
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(:get).once
16
- expect { subject.get(url) }.to raise_error(Http::Exceptions::HttpException)
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(:get).once
23
- expect { subject.get(url, {}, retries_on_failures: false) }.to raise_error(Http::Exceptions::HttpException)
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(:get).and_raise(SocketError).once.ordered
34
- expect(HTTParty).to receive(:get).and_return(response).ordered
35
- expect(Kernel).to receive(:sleep).with(delay_interval).once
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(:get).and_raise(SocketError).exactly(3).times.ordered
41
- expect(HTTParty).to receive(:get).and_return(response).ordered
42
- expect(Kernel).to receive(:sleep).with(delay_interval).once
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(:get).and_raise(SocketError).once.ordered
50
- expect(HTTParty).to receive(:get).and_return(response).ordered
51
- expect(Kernel).to receive(:sleep).with(delay_interval).once
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 'non 200 codes' do
58
- context '404 not found'do
59
- let(:url) { 'http://some.url.com' }
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
- it 'gets an error 404 and prints the error' do
68
- expect(HTTParty).to receive(:get).and_return(response)
69
- expect_any_instance_of(Logger).to receive(:warn).with('Status Code: 404, {"error":"some error","type":"some type"}')
70
- expect(subject)
71
- end
76
+ before do
77
+ allow(HTTParty).to receive(method).and_raise(SocketError)
78
+ end
72
79
 
73
- it 'returns nil' do
74
- expect(HTTParty).to receive(:get).and_return(response)
75
- expect(subject).to eq(nil)
76
- end
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' }
@@ -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
- context 'a run where the server 500s after a while' do
215
- before do
216
- allow_any_instance_of(http_client).to receive(:post).and_return('id' => 1)
217
- expect_any_instance_of(http_client).to receive(:get).twice.and_return(ok_progress)
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
- it 'should return true' do
224
- expect(described_class.start(valid_args)).to eq(true)
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.2
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-11 00:00:00.000000000 Z
12
+ date: 2016-10-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty