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