rainforest-cli 0.0.17 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +33 -8
- data/Rakefile +10 -0
- data/lib/rainforest/cli.rb +13 -158
- data/lib/rainforest/cli/git_trigger.rb +1 -1
- data/lib/rainforest/cli/http_client.rb +56 -0
- data/lib/rainforest/cli/options.rb +24 -1
- data/lib/rainforest/cli/runner.rb +151 -0
- data/lib/rainforest/cli/version.rb +1 -1
- data/spec/cli_spec.rb +48 -91
- data/spec/git_trigger_spec.rb +25 -3
- data/spec/options_spec.rb +107 -59
- data/spec/runner_spec.rb +59 -0
- data/spec/spec_helper.rb +2 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba717beff0c92befa7902d611e480080e717c607
|
4
|
+
data.tar.gz: 377bbc58b3ea971c1a028db37480245dfa808dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: daa1731fd6dab4c06c3d6f5ed07a81f7104b299498476eda2b370913e86dac4ebaf4dabc799305dfd647af5a5792949154bef81e8692a68f5a640bb3e4715606
|
7
|
+
data.tar.gz: 3bd19eb1863e67789b83aa6bf7597c61cf0a7d01cf442a7cc149ffb3fdad84b819e10246a4f51b64587392f549fa6e3c12fc381040f0ca7bdfa26c013b514bf8
|
data/README.md
CHANGED
@@ -4,42 +4,67 @@
|
|
4
4
|
|
5
5
|
A command line interface to interact with RainforestQA.
|
6
6
|
|
7
|
+
This is the easiest way to integrate Rainforest with your deploy scripts or CI server. See [our documentation](http://support.rainforestqa.com/hc/en-us/sections/200597986-Continuous-Integration) on the subject.
|
8
|
+
|
7
9
|
## Installation
|
8
10
|
|
9
|
-
|
11
|
+
You can install rainforest-cli with the [gem](https://rubygems.org/) utility.
|
12
|
+
|
13
|
+
```bash
|
14
|
+
gem install rainforest-cli
|
15
|
+
```
|
16
|
+
|
17
|
+
Alternatively, you can add to your Gemfile if you're in a ruby project.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem "rainforest-cli", require: false
|
21
|
+
```
|
10
22
|
|
11
23
|
## Basic Usage
|
12
24
|
To use the cli client, you'll need your API token from a test settings page from inside [Rainforest](https://app.rainforestqa.com/).
|
13
25
|
|
14
26
|
Run all of your tests
|
15
27
|
|
16
|
-
|
28
|
+
```bash
|
29
|
+
rainforest run all --token YOUR_TOKEN_HERE
|
30
|
+
```
|
17
31
|
|
18
32
|
Run all in the foreground and report
|
19
33
|
|
20
|
-
|
34
|
+
```bash
|
35
|
+
rainforest run all --fg --token YOUR_TOKEN_HERE
|
36
|
+
```
|
21
37
|
|
22
38
|
Run all tests with tag 'run-me' and abort previous in-progress runs.
|
23
39
|
|
24
|
-
|
40
|
+
```bash
|
41
|
+
rainforest run --tag run-me --fg --conflict abort --token YOUR_TOKEN_HERE
|
42
|
+
```
|
25
43
|
|
26
44
|
|
27
45
|
## Options
|
28
46
|
|
47
|
+
### General
|
48
|
+
|
29
49
|
Required:
|
30
50
|
- `--token <your-rainforest-token>` - you must supply your token (get it from any tests API tab)
|
31
51
|
|
52
|
+
|
53
|
+
### Running Tests
|
32
54
|
The options are:
|
33
55
|
|
34
56
|
- `--browsers ie8` or `--browsers ie8,chrome` - specficy the browsers you wish to run against. This overrides the test own settings. Valid browsers are ie8, ie9, chrome, firefox and safari.
|
35
57
|
- `--tag run-me` - only run tests which have this tag (recommended if you have lots of [test-steps](http://docs.rainforestqa.com/pages/example-test-suite.html#test_steps))!)
|
36
|
-
- `--conflict abort` - if you trigger rainforest more than once, anything running will be aborted and a fresh run started
|
37
|
-
- `--fail-fast` - fail the build as soon as the first failed result comes in. If you don't pass this it will wait until 100% of the run is done
|
38
|
-
- `--fg` - results in the foreground - this is what you want to make the build pass / fail dependent on rainforest results
|
39
58
|
- `--site-id` - only run tests for a specific site. Get in touch with us for help on getting that you site id if you are unable to.
|
40
|
-
- `--
|
59
|
+
- `--conflict abort` - if you trigger rainforest more than once, anything running will be aborted and a fresh run started
|
60
|
+
- `--fg` - results in the foreground - rainforest-cli will not return until the run is complete. This is what you want to make the build pass / fail dependent on rainforest results
|
61
|
+
- `--fail-fast` - fail the build as soon as the first failed result comes in. If you don't pass this it will wait until 100% of the run is done. Use with `--fg`.
|
62
|
+
- `--custom-url` - use a custom url for this run. Example use case: an ad-hoc QA environment with [Fourchette](https://github.com/rainforestapp/fourchette). You will need to specify a `site_id` too for this to work. Note that we will be creating a new environment for this particular run.
|
41
63
|
- `--git-trigger` - only trigger a run when the last commit (for a git repo in the current working directory) has contains `@rainforest` and a list of one or more tags. E.g. "Fix checkout process. @rainforest #checkout" would trigger a run for everything tagged `checkout`. This over-rides `--tag` and any tests specified. If no `@rainforest` is detected it will exit 0.
|
42
64
|
|
65
|
+
## Support
|
66
|
+
|
67
|
+
Email [help@rainforestqa.com](mailto:help@rainforestqa.com) if you're having trouble using this gem or need help to integrate Rainforest in your CI or deployment flow.
|
43
68
|
|
44
69
|
## Contributing
|
45
70
|
|
data/Rakefile
CHANGED
data/lib/rainforest/cli.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "rainforest/cli/version"
|
2
2
|
require "rainforest/cli/options"
|
3
|
+
require "rainforest/cli/runner"
|
4
|
+
require "rainforest/cli/http_client"
|
3
5
|
require "rainforest/cli/git_trigger"
|
4
6
|
require "rainforest/cli/csv_importer"
|
5
7
|
require "httparty"
|
@@ -8,176 +10,29 @@ require "logger"
|
|
8
10
|
|
9
11
|
module Rainforest
|
10
12
|
module Cli
|
11
|
-
API_URL = 'https://app.rainforestqa.com/api/1'.freeze
|
12
|
-
|
13
13
|
def self.start(args)
|
14
|
-
|
14
|
+
options = OptionParser.new(args)
|
15
15
|
OptionParser.new(['--help']) if args.size == 0
|
16
16
|
|
17
|
-
unless @options.token
|
18
|
-
logger.fatal "You must pass your API token using: --token TOKEN"
|
19
|
-
exit 2
|
20
|
-
end
|
21
|
-
|
22
|
-
if @options.custom_url && @options.site_id.nil?
|
23
|
-
logger.fatal "The site-id and custom-url options are both required."
|
24
|
-
exit 2
|
25
|
-
end
|
26
|
-
|
27
|
-
if @options.import_file_name && @options.import_name
|
28
|
-
unless File.exists?(@options.import_file_name)
|
29
|
-
logger.fatal "Input file: #{@options.import_file_name} not found"
|
30
|
-
exit 2
|
31
|
-
end
|
32
|
-
|
33
|
-
delete_generator(@options.import_name)
|
34
|
-
CSVImporter.new(@options.import_name, @options.import_file_name, @options.token).import
|
35
|
-
elsif @options.import_file_name || @options.import_name
|
36
|
-
logger.fatal "You must pass both --import-variable-csv-file and --import-variable-name"
|
37
|
-
exit 2
|
38
|
-
end
|
39
|
-
|
40
|
-
post_opts = {}
|
41
|
-
|
42
|
-
if @options.git_trigger?
|
43
|
-
logger.debug "Checking last git commit message:"
|
44
|
-
commit_message = GitTrigger.last_commit_message
|
45
|
-
logger.debug commit_message
|
46
|
-
|
47
|
-
# Show some messages to users about tests/tags being overriden
|
48
|
-
unless @options.tags.empty?
|
49
|
-
logger.warn "Specified tags are ignored when using --git-trigger"
|
50
|
-
else
|
51
|
-
logger.warn "Specified tests are ignored when using --git-trigger"
|
52
|
-
end
|
53
|
-
|
54
|
-
if GitTrigger.git_trigger_should_run?(commit_message)
|
55
|
-
tags = GitTrigger.extract_hashtags(commit_message)
|
56
|
-
if tags.empty?
|
57
|
-
logger.error "Triggered via git, but no hashtags detected. Please use commit message format:"
|
58
|
-
logger.error "\t'some message. @rainforest #tag1 #tag2"
|
59
|
-
exit 2
|
60
|
-
else
|
61
|
-
post_opts[:tags] = [tags.join(',')]
|
62
|
-
end
|
63
|
-
else
|
64
|
-
logger.info "Not triggering as @rainforest was not mentioned in last commit message."
|
65
|
-
exit 0
|
66
|
-
end
|
67
|
-
else
|
68
|
-
# Not using git_trigger, so look for the
|
69
|
-
if !@options.tags.empty?
|
70
|
-
post_opts[:tags] = @options.tags
|
71
|
-
else
|
72
|
-
post_opts[:tests] = @options.tests
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
post_opts[:conflict] = @options.conflict if @options.conflict
|
77
|
-
post_opts[:browsers] = @options.browsers if @options.browsers
|
78
|
-
post_opts[:site_id] = @options.site_id if @options.site_id
|
79
|
-
post_opts[:gem_version] = Rainforest::Cli::VERSION
|
80
|
-
|
81
|
-
post_opts[:environment_id] = get_environment_id(@options.custom_url) if @options.custom_url
|
82
|
-
|
83
|
-
logger.debug "POST options: #{post_opts.inspect}"
|
84
|
-
logger.info "Issuing run"
|
85
|
-
|
86
|
-
response = post(API_URL + '/runs', post_opts)
|
87
|
-
|
88
|
-
if response['error']
|
89
|
-
logger.fatal "Error starting your run: #{response['error']}"
|
90
|
-
exit 1
|
91
|
-
end
|
92
|
-
|
93
|
-
run_id = response["id"]
|
94
|
-
running = true
|
95
|
-
|
96
|
-
return unless @options.foreground?
|
97
|
-
|
98
|
-
while running
|
99
|
-
Kernel.sleep 5
|
100
|
-
response = get "#{API_URL}/runs/#{run_id}?gem_version=#{Rainforest::Cli::VERSION}"
|
101
|
-
if response
|
102
|
-
if %w(queued validating in_progress sending_webhook waiting_for_callback).include?(response["state"])
|
103
|
-
logger.info "Run #{run_id} is #{response['state']} and is #{response['current_progress']['percent']}% complete"
|
104
|
-
running = false if response["result"] == 'failed' && @options.failfast?
|
105
|
-
else
|
106
|
-
logger.info "Run #{run_id} is now #{response["state"]} and has #{response["result"]}"
|
107
|
-
running = false
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
if response["result"] != "passed"
|
113
|
-
exit 1
|
114
|
-
end
|
115
|
-
true
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.list_generators
|
119
|
-
get("#{API_URL}/generators")
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.delete_generator(name)
|
123
|
-
generator = list_generators.find {|g| g['generator_type'] == 'tabular' && g['name'] == name }
|
124
|
-
delete("#{API_URL}/generators/#{generator['id']}") if generator
|
125
|
-
end
|
126
|
-
|
127
|
-
def self.delete(url, body = {})
|
128
|
-
response = HTTParty.delete url, {
|
129
|
-
body: body,
|
130
|
-
headers: {"CLIENT_TOKEN" => @options.token}
|
131
|
-
}
|
132
|
-
|
133
|
-
JSON.parse(response.body)
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.post(url, body = {})
|
137
|
-
response = HTTParty.post url, {
|
138
|
-
body: body,
|
139
|
-
headers: {"CLIENT_TOKEN" => @options.token}
|
140
|
-
}
|
141
|
-
|
142
|
-
JSON.parse(response.body)
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.get(url, body = {})
|
146
|
-
response = HTTParty.get url, {
|
147
|
-
body: body,
|
148
|
-
headers: {"CLIENT_TOKEN" => @options.token}
|
149
|
-
}
|
150
|
-
|
151
|
-
if response.code == 200
|
152
|
-
JSON.parse(response.body)
|
153
|
-
else
|
154
|
-
nil
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.get_environment_id url
|
159
17
|
begin
|
160
|
-
|
161
|
-
rescue
|
162
|
-
logger.fatal
|
18
|
+
options.validate!
|
19
|
+
rescue OptionParser::ValidationError => e
|
20
|
+
logger.fatal e.message
|
163
21
|
exit 2
|
164
22
|
end
|
165
23
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
if environment['error']
|
170
|
-
# I am talking about a URL here because the environments are pretty
|
171
|
-
# much hidden from clients so far.
|
172
|
-
logger.fatal "Error creating the ad-hoc URL: #{environment['error']}"
|
173
|
-
exit 1
|
174
|
-
end
|
24
|
+
runner = Runner.new(options)
|
25
|
+
runner.run
|
175
26
|
|
176
|
-
|
27
|
+
true
|
177
28
|
end
|
178
29
|
|
179
30
|
def self.logger
|
180
31
|
@logger ||= Logger.new(STDOUT)
|
181
32
|
end
|
33
|
+
|
34
|
+
def self.logger=(logger)
|
35
|
+
@logger = logger
|
36
|
+
end
|
182
37
|
end
|
183
38
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Rainforest
|
2
|
+
module Cli
|
3
|
+
class HttpClient
|
4
|
+
API_URL = ENV.fetch("RAINFOREST_API_URL") do
|
5
|
+
'https://app.rainforestqa.com/api/1'
|
6
|
+
end.freeze
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@token = options.fetch(:token)
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(url, body = {})
|
13
|
+
response = HTTParty.delete make_url(url), {
|
14
|
+
body: body,
|
15
|
+
headers: headers,
|
16
|
+
}
|
17
|
+
|
18
|
+
JSON.parse(response.body)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(url, body = {})
|
22
|
+
response = HTTParty.post make_url(url), {
|
23
|
+
body: body,
|
24
|
+
headers: headers,
|
25
|
+
}
|
26
|
+
|
27
|
+
JSON.parse(response.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(url, body = {})
|
31
|
+
response = HTTParty.get make_url(url), {
|
32
|
+
body: body,
|
33
|
+
headers: headers,
|
34
|
+
}
|
35
|
+
|
36
|
+
if response.code == 200
|
37
|
+
JSON.parse(response.body)
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def make_url(url)
|
45
|
+
File.join(API_URL, url)
|
46
|
+
end
|
47
|
+
|
48
|
+
def headers
|
49
|
+
{
|
50
|
+
"CLIENT_TOKEN" => @token,
|
51
|
+
"User-Agent" => "Rainforest-cli-#{Rainforest::Cli::VERSION}"
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -66,7 +66,7 @@ module Rainforest
|
|
66
66
|
opts.on("--custom-url URL", String, "Use a custom url for this run. You will need to specify a site_id too for this to work.") do |value|
|
67
67
|
@custom_url = value
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
opts.on_tail("--help", "Display help message and exit") do |value|
|
71
71
|
puts opts
|
72
72
|
exit 0
|
@@ -92,6 +92,29 @@ module Rainforest
|
|
92
92
|
def foreground?
|
93
93
|
@foreground
|
94
94
|
end
|
95
|
+
|
96
|
+
def validate!
|
97
|
+
unless token
|
98
|
+
raise ValidationError, "You must pass your API token using: --token TOKEN"
|
99
|
+
end
|
100
|
+
|
101
|
+
if custom_url && site_id.nil?
|
102
|
+
raise ValidationError, "The site-id and custom-url options are both required."
|
103
|
+
end
|
104
|
+
|
105
|
+
if import_file_name && import_name
|
106
|
+
unless File.exists?(import_file_name)
|
107
|
+
raise ValidationError, "Input file: #{import_file_name} not found"
|
108
|
+
end
|
109
|
+
|
110
|
+
elsif import_file_name || import_name
|
111
|
+
raise ValidationError, "You must pass both --import-variable-csv-file and --import-variable-name"
|
112
|
+
end
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
class ValidationError < RuntimeError
|
117
|
+
end
|
95
118
|
end
|
96
119
|
end
|
97
120
|
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Rainforest
|
2
|
+
module Cli
|
3
|
+
class Runner
|
4
|
+
attr_reader :options, :client
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
@client = HttpClient.new token: options.token
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
if options.import_file_name && options.import_name
|
13
|
+
delete_generator(options.import_name)
|
14
|
+
CSVImporter.new(options.import_name, options.import_file_name, options.token).import
|
15
|
+
end
|
16
|
+
|
17
|
+
post_opts = make_create_run_options
|
18
|
+
|
19
|
+
logger.debug "POST options: #{post_opts.inspect}"
|
20
|
+
logger.info "Issuing run"
|
21
|
+
|
22
|
+
response = client.post('/runs', post_opts)
|
23
|
+
|
24
|
+
if response['error']
|
25
|
+
logger.fatal "Error starting your run: #{response['error']}"
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
if options.foreground?
|
31
|
+
run_id = response.fetch("id")
|
32
|
+
wait_for_run_completion(run_id)
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def wait_for_run_completion(run_id)
|
39
|
+
running = true
|
40
|
+
while running
|
41
|
+
Kernel.sleep 5
|
42
|
+
response = client.get("/runs/#{run_id}")
|
43
|
+
if response
|
44
|
+
state_details = response.fetch('state_details')
|
45
|
+
unless state_details.fetch("is_final_state")
|
46
|
+
logger.info "Run #{run_id} is #{response['state']} and is #{response['current_progress']['percent']}% complete"
|
47
|
+
running = false if response["result"] == 'failed' && options.failfast?
|
48
|
+
else
|
49
|
+
logger.info "Run #{run_id} is now #{response["state"]} and has #{response["result"]}"
|
50
|
+
running = false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if url = response["frontend_url"]
|
56
|
+
logger.info "The detailed results are available at #{url}"
|
57
|
+
end
|
58
|
+
|
59
|
+
if response["result"] != "passed"
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def make_create_run_options
|
65
|
+
post_opts = {}
|
66
|
+
if options.git_trigger?
|
67
|
+
logger.debug "Checking last git commit message:"
|
68
|
+
commit_message = GitTrigger.last_commit_message
|
69
|
+
logger.debug commit_message
|
70
|
+
|
71
|
+
# Show some messages to users about tests/tags being overriden
|
72
|
+
unless options.tags.empty?
|
73
|
+
logger.warn "Specified tags are ignored when using --git-trigger"
|
74
|
+
else
|
75
|
+
logger.warn "Specified tests are ignored when using --git-trigger"
|
76
|
+
end
|
77
|
+
|
78
|
+
if GitTrigger.git_trigger_should_run?(commit_message)
|
79
|
+
tags = GitTrigger.extract_hashtags(commit_message)
|
80
|
+
if tags.empty?
|
81
|
+
logger.error "Triggered via git, but no hashtags detected. Please use commit message format:"
|
82
|
+
logger.error "\t'some message. @rainforest #tag1 #tag2"
|
83
|
+
exit 2
|
84
|
+
else
|
85
|
+
post_opts[:tags] = [tags.join(',')]
|
86
|
+
end
|
87
|
+
else
|
88
|
+
logger.info "Not triggering as @rainforest was not mentioned in last commit message."
|
89
|
+
exit 0
|
90
|
+
end
|
91
|
+
else
|
92
|
+
# Not using git_trigger, so look for the
|
93
|
+
if !options.tags.empty?
|
94
|
+
post_opts[:tags] = options.tags
|
95
|
+
else
|
96
|
+
post_opts[:tests] = options.tests
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
post_opts[:conflict] = options.conflict if options.conflict
|
101
|
+
post_opts[:browsers] = options.browsers if options.browsers
|
102
|
+
post_opts[:site_id] = options.site_id if options.site_id
|
103
|
+
|
104
|
+
if options.custom_url
|
105
|
+
post_opts[:environment_id] = get_environment_id(options.custom_url)
|
106
|
+
end
|
107
|
+
|
108
|
+
post_opts
|
109
|
+
end
|
110
|
+
|
111
|
+
def logger
|
112
|
+
Rainforest::Cli.logger
|
113
|
+
end
|
114
|
+
|
115
|
+
def list_generators
|
116
|
+
client.get("/generators")
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_generator(name)
|
120
|
+
generator = list_generators.find {|g| g['generator_type'] == 'tabular' && g['name'] == name }
|
121
|
+
client.delete("generators/#{generator['id']}") if generator
|
122
|
+
end
|
123
|
+
|
124
|
+
def url_valid?(url)
|
125
|
+
return false unless URI::regexp === url
|
126
|
+
|
127
|
+
uri = URI.parse(url)
|
128
|
+
%w(http https).include?(uri.scheme)
|
129
|
+
end
|
130
|
+
|
131
|
+
def get_environment_id url
|
132
|
+
unless url_valid?(url)
|
133
|
+
logger.fatal "The custom URL is invalid"
|
134
|
+
exit 2
|
135
|
+
end
|
136
|
+
|
137
|
+
env_post_body = { name: 'temporary-env-for-custom-url-via-CLI', url: url }
|
138
|
+
environment = client.post("/environments", env_post_body)
|
139
|
+
|
140
|
+
if environment['error']
|
141
|
+
# I am talking about a URL here because the environments are pretty
|
142
|
+
# much hidden from clients so far.
|
143
|
+
logger.fatal "Error creating the ad-hoc URL: #{environment['error']}"
|
144
|
+
exit 1
|
145
|
+
end
|
146
|
+
|
147
|
+
return environment['id']
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/spec/cli_spec.rb
CHANGED
@@ -1,52 +1,38 @@
|
|
1
1
|
describe Rainforest::Cli do
|
2
|
+
let(:http_client) { Rainforest::Cli::HttpClient.any_instance }
|
3
|
+
|
2
4
|
before do
|
3
5
|
Kernel.stub(:sleep)
|
4
|
-
stub_const("Rainforest::Cli::API_URL", 'http://app.rainforest.dev/api/1')
|
5
6
|
end
|
6
7
|
|
7
8
|
describe ".start" do
|
8
9
|
let(:valid_args) { %w(--token foo run --fg all) }
|
9
|
-
let(:ok_progress)
|
10
|
+
let(:ok_progress) do
|
11
|
+
{
|
12
|
+
"state" => "in_progress",
|
13
|
+
"current_progress" => {"percent" => "1"},
|
14
|
+
"state_details" => { "is_final_state" => false },
|
15
|
+
"result" => "no_result",
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:complete_response) do
|
20
|
+
{
|
21
|
+
"state" => "complete",
|
22
|
+
"current_progress" => {"percent" => "100"},
|
23
|
+
"state_details" => { "is_final_state" => true},
|
24
|
+
"result" => "passed",
|
25
|
+
}
|
26
|
+
end
|
10
27
|
|
11
28
|
context "with bad parameters" do
|
12
29
|
context "no token" do
|
13
30
|
let(:params) { %w(--custom-url http://ad-hoc.example.com) }
|
14
31
|
it 'errors out' do
|
15
32
|
expect_any_instance_of(Logger).to receive(:fatal).with('You must pass your API token using: --token TOKEN')
|
16
|
-
begin
|
17
|
-
described_class.start(params)
|
18
|
-
rescue SystemExit => e
|
19
|
-
# That's fine, this is expected but tested in a differnet assertion
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'exits with exit code 2' do
|
24
|
-
expect {
|
25
|
-
described_class.start(params)
|
26
|
-
}.to raise_error { |error|
|
27
|
-
expect(error).to be_a(SystemExit)
|
28
|
-
expect(error.status).to eq 2
|
29
|
-
}
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context "with custom-url with no site-id" do
|
34
|
-
let(:params) { %w(--token x --custom-url http://ad-hoc.example.com) }
|
35
|
-
|
36
|
-
it 'errors out' do
|
37
|
-
expect_any_instance_of(Logger).to receive(:fatal).with('The site-id and custom-url options are both required.')
|
38
|
-
begin
|
39
|
-
described_class.start(params)
|
40
|
-
rescue SystemExit => e
|
41
|
-
# That's fine, this is expected but tested in a differnet assertion
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'exits with exit code 2' do
|
46
33
|
expect {
|
47
34
|
described_class.start(params)
|
48
|
-
}.to raise_error { |error|
|
49
|
-
expect(error).to be_a(SystemExit)
|
35
|
+
}.to raise_error(SystemExit) { |error|
|
50
36
|
expect(error.status).to eq 2
|
51
37
|
}
|
52
38
|
end
|
@@ -68,23 +54,23 @@ describe Rainforest::Cli do
|
|
68
54
|
before do
|
69
55
|
Rainforest::Cli::GitTrigger.stub(:last_commit_message) { commit_message }
|
70
56
|
end
|
71
|
-
|
57
|
+
|
72
58
|
describe "with tags parameter passed" do
|
73
59
|
let(:params) { %w(--token x --tag x --git-trigger) }
|
74
60
|
|
75
61
|
it "warns about the parameter being ignored" do
|
76
62
|
expect_any_instance_of(Logger).to receive(:warn).with("Specified tags are ignored when using --git-trigger")
|
77
|
-
|
63
|
+
|
78
64
|
start_with_params(params, 0)
|
79
65
|
end
|
80
66
|
end
|
81
|
-
|
67
|
+
|
82
68
|
describe "with tags parameter passed" do
|
83
69
|
let(:params) { %w(all --token x --git-trigger) }
|
84
70
|
|
85
71
|
it "warns about the parameter being ignored" do
|
86
72
|
expect_any_instance_of(Logger).to receive(:warn).with("Specified tests are ignored when using --git-trigger")
|
87
|
-
|
73
|
+
|
88
74
|
start_with_params(params, 0)
|
89
75
|
end
|
90
76
|
end
|
@@ -102,7 +88,7 @@ describe Rainforest::Cli do
|
|
102
88
|
it "exit 2's and logs the reason" do
|
103
89
|
expect_any_instance_of(Logger).to receive(:error).with("Triggered via git, but no hashtags detected. Please use commit message format:")
|
104
90
|
expect_any_instance_of(Logger).to receive(:error).with("\t'some message. @rainforest #tag1 #tag2")
|
105
|
-
|
91
|
+
|
106
92
|
start_with_params(params, 2)
|
107
93
|
end
|
108
94
|
end
|
@@ -111,10 +97,11 @@ describe Rainforest::Cli do
|
|
111
97
|
let(:commit_message) { 'a test commit message @rainforest #run-me' }
|
112
98
|
|
113
99
|
it "starts the run with the specified tags" do
|
114
|
-
|
115
|
-
"
|
116
|
-
|
117
|
-
|
100
|
+
http_client.should_receive(:post) do |url, options|
|
101
|
+
expect(url).to eq("/runs")
|
102
|
+
expect(options[:tags]).to eq(['run-me'])
|
103
|
+
{}
|
104
|
+
end
|
118
105
|
|
119
106
|
start_with_params(params, 0)
|
120
107
|
end
|
@@ -124,9 +111,7 @@ describe Rainforest::Cli do
|
|
124
111
|
context "with site-id and custom-url" do
|
125
112
|
let(:params) { %w(--token x --site 3 --custom-url http://ad-hoc.example.com) }
|
126
113
|
it "creates a new environment" do
|
127
|
-
|
128
|
-
expect(described_class).to receive(:post).with(
|
129
|
-
"http://app.rainforest.dev/api/1/environments",
|
114
|
+
http_client.should_receive(:post).with("/environments",
|
130
115
|
{
|
131
116
|
:name => "temporary-env-for-custom-url-via-CLI",
|
132
117
|
:url=>"http://ad-hoc.example.com"
|
@@ -135,6 +120,8 @@ describe Rainforest::Cli do
|
|
135
120
|
{ 'id' => 333 }
|
136
121
|
)
|
137
122
|
|
123
|
+
http_client.should_receive(:post).with("/runs", anything).and_return( { "id" => 1 } )
|
124
|
+
|
138
125
|
# This is a hack because when expecting a function to be called with
|
139
126
|
# parameters, the last call is compared but I want to compare the first
|
140
127
|
# call, not the call to create a run, so I exit, but rescue from it here
|
@@ -147,11 +134,11 @@ describe Rainforest::Cli do
|
|
147
134
|
end
|
148
135
|
|
149
136
|
it "starts the run with site_id and environment_id" do
|
150
|
-
|
137
|
+
Rainforest::Cli::Runner.any_instance.stub(get_environment_id: 333)
|
151
138
|
|
152
|
-
|
153
|
-
"
|
154
|
-
{ :tests=>[], :site_id=>3, :
|
139
|
+
http_client.should_receive(:post).with(
|
140
|
+
"/runs",
|
141
|
+
{ :tests=>[], :site_id=>3, :environment_id=>333 }
|
155
142
|
).and_return( {} )
|
156
143
|
described_class.start(params)
|
157
144
|
end
|
@@ -159,11 +146,11 @@ describe Rainforest::Cli do
|
|
159
146
|
|
160
147
|
context "a simple run" do
|
161
148
|
before do
|
162
|
-
|
163
|
-
3.times do
|
164
|
-
|
149
|
+
http_client.stub(:post) { {"id" => 1} }
|
150
|
+
3.times do
|
151
|
+
http_client.should_receive(:get) { ok_progress }
|
165
152
|
end
|
166
|
-
|
153
|
+
http_client.should_receive(:get) { complete_response }
|
167
154
|
end
|
168
155
|
|
169
156
|
it "should return true" do
|
@@ -173,18 +160,18 @@ describe Rainforest::Cli do
|
|
173
160
|
|
174
161
|
context "a run where the server 500s after a while" do
|
175
162
|
before do
|
176
|
-
|
177
|
-
2.times do
|
178
|
-
|
163
|
+
http_client.stub(:post) { {"id" => 1} }
|
164
|
+
2.times do
|
165
|
+
http_client.should_receive(:get) { ok_progress }
|
179
166
|
end
|
180
167
|
|
181
|
-
|
168
|
+
http_client.should_receive(:get) { nil }
|
182
169
|
|
183
|
-
2.times do
|
184
|
-
|
170
|
+
2.times do
|
171
|
+
http_client.should_receive(:get) { ok_progress }
|
185
172
|
end
|
186
173
|
|
187
|
-
|
174
|
+
http_client.should_receive(:get) { complete_response }
|
188
175
|
end
|
189
176
|
|
190
177
|
it "should return true" do
|
@@ -192,34 +179,4 @@ describe Rainforest::Cli do
|
|
192
179
|
end
|
193
180
|
end
|
194
181
|
end
|
195
|
-
|
196
|
-
describe ".get_environment_id" do
|
197
|
-
context "with an invalid URL" do
|
198
|
-
xit 'errors out and exits' do
|
199
|
-
expect_any_instance_of(Logger).to receive(:fatal).with("The custom URL is invalid")
|
200
|
-
expect {
|
201
|
-
described_class.get_environment_id('http://some=weird')
|
202
|
-
}.to raise_error { |error|
|
203
|
-
expect(error).to be_a(SystemExit)
|
204
|
-
expect(error.status).to eq 2
|
205
|
-
}
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
context 'on API error' do
|
210
|
-
before do
|
211
|
-
allow(described_class).to receive(:post).and_return( {"error"=>"Some API error"} )
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'errors out and exits' do
|
215
|
-
expect_any_instance_of(Logger).to receive(:fatal).with("Error creating the ad-hoc URL: Some API error")
|
216
|
-
expect {
|
217
|
-
described_class.get_environment_id('http://example.com')
|
218
|
-
}.to raise_error { |error|
|
219
|
-
expect(error).to be_a(SystemExit)
|
220
|
-
expect(error.status).to eq 1
|
221
|
-
}
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
182
|
end
|
data/spec/git_trigger_spec.rb
CHANGED
@@ -1,13 +1,25 @@
|
|
1
1
|
describe Rainforest::Cli::GitTrigger do
|
2
2
|
subject { described_class }
|
3
3
|
|
4
|
+
let(:default_dir) { __dir__ }
|
5
|
+
let(:test_repo_dir) { File.join(Dir.tmpdir, 'raiforest-cli', 'test-repo') }
|
6
|
+
|
4
7
|
describe ".last_commit_message" do
|
5
|
-
|
8
|
+
around(:each) do |spec|
|
6
9
|
default_dir = Dir.pwd
|
10
|
+
FileUtils.mkdir_p test_repo_dir
|
11
|
+
Dir.chdir(test_repo_dir)
|
12
|
+
setup_test_repo
|
13
|
+
begin
|
14
|
+
spec.call
|
15
|
+
ensure
|
16
|
+
Dir.chdir(default_dir)
|
17
|
+
end
|
18
|
+
end
|
7
19
|
|
8
|
-
|
20
|
+
# Commented because it kills CI
|
21
|
+
xit "returns a string" do
|
9
22
|
expect(described_class.last_commit_message).to eq "Initial commit"
|
10
|
-
Dir.chdir(default_dir)
|
11
23
|
end
|
12
24
|
end
|
13
25
|
|
@@ -26,4 +38,14 @@ describe Rainforest::Cli::GitTrigger do
|
|
26
38
|
expect(described_class.extract_hashtags('#dashes-work, #underscores_work #007')).to eq ['dashes-work', 'underscores_work', '007']
|
27
39
|
end
|
28
40
|
end
|
41
|
+
|
42
|
+
def setup_test_repo
|
43
|
+
FileUtils.rm_rf File.join(test_repo_dir, "*")
|
44
|
+
[
|
45
|
+
"git init",
|
46
|
+
"git commit --allow-empty -m 'Initial commit'",
|
47
|
+
].each do |cmd|
|
48
|
+
system cmd
|
49
|
+
end
|
50
|
+
end
|
29
51
|
end
|
data/spec/options_spec.rb
CHANGED
@@ -1,80 +1,128 @@
|
|
1
1
|
describe Rainforest::Cli::OptionParser do
|
2
2
|
subject { Rainforest::Cli::OptionParser.new(args) }
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
describe "#initialize" do
|
5
|
+
context "importing csv file" do
|
6
|
+
let(:args) { ["--import-variable-csv-file", "some_file.csv"] }
|
7
|
+
its(:import_file_name) { should == "some_file.csv" }
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
context "importing name" do
|
11
|
+
let(:args) { ["--import-variable-name", "some_name"] }
|
12
|
+
its(:import_name) { should == "some_name" }
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
context "run all tests" do
|
16
|
+
let(:args) { ["run", "all"] }
|
17
|
+
its(:tests) { should == ["all"]}
|
18
|
+
its(:conflict) { should == nil}
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
context "run from tags" do
|
22
|
+
let(:args) { ["run", "--tag", "run-me"] }
|
23
|
+
its(:tests) { should == []}
|
24
|
+
its(:tags) { should == ["run-me"]}
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
context "only run in specific browsers" do
|
28
|
+
let(:args) { ["run", "--browsers", "ie8"] }
|
29
|
+
its(:browsers) { should == ["ie8"]}
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
context "raises errors with invalid browsers" do
|
33
|
+
let(:args) { ["run", "--browsers", "lulbrower"] }
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
it "raises an exception" do
|
36
|
+
expect{subject}.to raise_error(Rainforest::Cli::BrowserException)
|
37
|
+
end
|
36
38
|
end
|
37
|
-
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
context "accepts multiple browsers" do
|
41
|
+
let(:args) { ["run", "--browsers", "ie8,chrome"] }
|
42
|
+
its(:browsers) { should == ["ie8", "chrome"]}
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
context "it parses the --git-trigger flag" do
|
46
|
+
let(:args) { ["run", "--git-trigger", "all"] }
|
47
|
+
its(:tests) { should == ["all"]}
|
48
|
+
its(:git_trigger?) { should be_true }
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
context "it parses the --fg flag" do
|
52
|
+
let(:args) { ["run", "--fg", "all"] }
|
53
|
+
its(:tests) { should == ["all"]}
|
54
|
+
its(:foreground?) { should be_true }
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
context "it parses the api token" do
|
58
|
+
let(:args) { ["run", "--token", "abc", "all"] }
|
59
|
+
its(:token) { should == "abc"}
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
context "it parses the conflict flag" do
|
63
|
+
let(:args) { ["run", "--conflict", "abort", "all"] }
|
64
|
+
its(:conflict) { should == "abort"}
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
context "it parses the fail-fast flag" do
|
68
|
+
let(:args) { ["run", "--fail-fast"] }
|
69
|
+
its(:failfast?) { should be_true }
|
70
|
+
end
|
71
|
+
|
72
|
+
context "it parses the site-id flag" do
|
73
|
+
let(:args) { ["run", "--site-id", '3'] }
|
74
|
+
its(:site_id) { should eq 3 }
|
75
|
+
end
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
context "it parses the custom-url flag" do
|
78
|
+
let(:args) { ["run", "--custom-url", 'http://ad-hoc.example.com'] }
|
79
|
+
its(:custom_url) { should eq 'http://ad-hoc.example.com' }
|
80
|
+
end
|
74
81
|
end
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
|
83
|
+
describe "#validate!" do
|
84
|
+
def does_not_raise_a_validation_exception
|
85
|
+
expect do
|
86
|
+
subject.validate!
|
87
|
+
end.to_not raise_error
|
88
|
+
end
|
89
|
+
|
90
|
+
def raises_a_validation_exception
|
91
|
+
expect do
|
92
|
+
subject.validate!
|
93
|
+
end.to raise_error(described_class::ValidationError)
|
94
|
+
end
|
95
|
+
|
96
|
+
context "with valid arguments" do
|
97
|
+
let(:args) { %w(--token foo) }
|
98
|
+
it { does_not_raise_a_validation_exception }
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with missing token" do
|
102
|
+
let(:args) { %w() }
|
103
|
+
it { raises_a_validation_exception }
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with a custom url but no site id" do
|
107
|
+
let(:args) { %w(--token foo --custom-url http://www.example.com) }
|
108
|
+
it { raises_a_validation_exception }
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with a import_file_name but no import name" do
|
112
|
+
let(:args) { %w(--token foo --import-variable-csv-file foo.csv) }
|
113
|
+
it { raises_a_validation_exception }
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with a import_file_name and a import_name" do
|
117
|
+
context "for an existing file" do
|
118
|
+
let(:args) { %W(--token foo --import-variable-csv-file #{__FILE__} --import-variable-name my-var) }
|
119
|
+
it { does_not_raise_a_validation_exception }
|
120
|
+
end
|
121
|
+
|
122
|
+
context "for a non existing file" do
|
123
|
+
let(:args) { %W(--token foo --import-variable-csv-file does_not_exist --import-variable-name my-var) }
|
124
|
+
it { raises_a_validation_exception }
|
125
|
+
end
|
126
|
+
end
|
79
127
|
end
|
80
128
|
end
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
describe Rainforest::Cli::Runner do
|
2
|
+
let(:args) { %w(run all) }
|
3
|
+
let(:options) { Rainforest::Cli::OptionParser.new(args) }
|
4
|
+
subject { described_class.new(options) }
|
5
|
+
|
6
|
+
describe "#get_environment_id" do
|
7
|
+
context "with an invalid URL" do
|
8
|
+
it 'errors out and exits' do
|
9
|
+
expect {
|
10
|
+
subject.get_environment_id('some=weird')
|
11
|
+
}.to raise_error(SystemExit) { |error|
|
12
|
+
expect(error.status).to eq 2
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'on API error' do
|
18
|
+
before do
|
19
|
+
allow(subject.client).to receive(:post).and_return( {"error"=>"Some API error"} )
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'errors out and exits' do
|
23
|
+
expect_any_instance_of(Logger).to receive(:fatal).with("Error creating the ad-hoc URL: Some API error")
|
24
|
+
expect {
|
25
|
+
subject.get_environment_id('http://example.com')
|
26
|
+
}.to raise_error(SystemExit) { |error|
|
27
|
+
expect(error.status).to eq 1
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#url_valid?" do
|
34
|
+
subject { super().url_valid?(url) }
|
35
|
+
[
|
36
|
+
"http://example.org",
|
37
|
+
"https://example.org",
|
38
|
+
"http://example.org/",
|
39
|
+
"http://example.org?foo=bar",
|
40
|
+
].each do |valid_url|
|
41
|
+
context "#{valid_url}" do
|
42
|
+
let(:url) { valid_url }
|
43
|
+
it { should be(true) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
[
|
48
|
+
"ftp://example.org",
|
49
|
+
"example.org",
|
50
|
+
"",
|
51
|
+
].each do |valid_url|
|
52
|
+
context "#{valid_url}" do
|
53
|
+
let(:url) { valid_url }
|
54
|
+
it { should be(false) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/spec/spec_helper.rb
CHANGED
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: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Mathieu
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-01-
|
12
|
+
date: 2015-01-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httparty
|
@@ -105,12 +105,15 @@ files:
|
|
105
105
|
- lib/rainforest/cli.rb
|
106
106
|
- lib/rainforest/cli/csv_importer.rb
|
107
107
|
- lib/rainforest/cli/git_trigger.rb
|
108
|
+
- lib/rainforest/cli/http_client.rb
|
108
109
|
- lib/rainforest/cli/options.rb
|
110
|
+
- lib/rainforest/cli/runner.rb
|
109
111
|
- lib/rainforest/cli/version.rb
|
110
112
|
- rainforest-cli.gemspec
|
111
113
|
- spec/cli_spec.rb
|
112
114
|
- spec/git_trigger_spec.rb
|
113
115
|
- spec/options_spec.rb
|
116
|
+
- spec/runner_spec.rb
|
114
117
|
- spec/spec_helper.rb
|
115
118
|
homepage: https://www.rainforestqa.com/
|
116
119
|
licenses:
|
@@ -140,4 +143,5 @@ test_files:
|
|
140
143
|
- spec/cli_spec.rb
|
141
144
|
- spec/git_trigger_spec.rb
|
142
145
|
- spec/options_spec.rb
|
146
|
+
- spec/runner_spec.rb
|
143
147
|
- spec/spec_helper.rb
|