rainforest-cli 1.2.0 → 1.2.1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +5 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile +7 -1
  8. data/README.md +2 -0
  9. data/Rakefile +6 -4
  10. data/circle.yml +3 -0
  11. data/lib/rainforest/cli.rb +20 -16
  12. data/lib/rainforest/cli/constants.rb +4 -0
  13. data/lib/rainforest/cli/csv_importer.rb +6 -6
  14. data/lib/rainforest/cli/git_trigger.rb +2 -1
  15. data/lib/rainforest/cli/http_client.rb +50 -13
  16. data/lib/rainforest/cli/options.rb +71 -39
  17. data/lib/rainforest/cli/remote_tests.rb +49 -0
  18. data/lib/rainforest/cli/runner.rb +19 -17
  19. data/lib/rainforest/cli/test_files.rb +32 -14
  20. data/lib/rainforest/cli/test_importer.rb +35 -155
  21. data/lib/rainforest/cli/test_parser.rb +38 -14
  22. data/lib/rainforest/cli/uploader.rb +107 -0
  23. data/lib/rainforest/cli/validator.rb +158 -0
  24. data/lib/rainforest/cli/version.rb +2 -1
  25. data/rainforest-cli.gemspec +14 -12
  26. data/spec/cli_spec.rb +84 -90
  27. data/spec/csv_importer_spec.rb +13 -8
  28. data/spec/git_trigger_spec.rb +28 -15
  29. data/spec/http_client_spec.rb +57 -0
  30. data/spec/options_spec.rb +72 -70
  31. data/spec/rainforest-example/example_test.rfml +2 -1
  32. data/spec/remote_tests_spec.rb +22 -0
  33. data/spec/runner_spec.rb +17 -16
  34. data/spec/spec_helper.rb +16 -9
  35. data/spec/test_files_spec.rb +20 -24
  36. data/spec/uploader_spec.rb +54 -0
  37. data/spec/validation-examples/circular_embeds/test1.rfml +5 -0
  38. data/spec/validation-examples/circular_embeds/test2.rfml +5 -0
  39. data/spec/validation-examples/correct_embeds/embedded_test.rfml +6 -0
  40. data/spec/validation-examples/correct_embeds/test_with_embedded.rfml +8 -0
  41. data/spec/validation-examples/missing_embeds/correct_test.rfml +8 -0
  42. data/spec/validation-examples/missing_embeds/incorrect_test.rfml +8 -0
  43. data/spec/validation-examples/parse_errors/no_parse_errors.rfml +6 -0
  44. data/spec/validation-examples/parse_errors/no_question.rfml +5 -0
  45. data/spec/validation-examples/parse_errors/no_question_mark.rfml +6 -0
  46. data/spec/validation-examples/parse_errors/no_rfml_id.rfml +5 -0
  47. data/spec/validator_spec.rb +119 -0
  48. metadata +96 -16
@@ -1,10 +1,19 @@
1
+ # frozen_string_literal: true
1
2
  describe RainforestCli::CSVImporter do
2
3
  let(:csv_file) { "#{File.dirname(__FILE__)}/fixtures/variables.txt" }
3
4
 
4
5
  describe '.import' do
5
6
  subject { described_class.new('variables', csv_file, 'abc123') }
7
+ let(:progressbar_mock) { double('ProgressBar') }
8
+
9
+ before do
10
+ # suppress output in terminal
11
+ allow_any_instance_of(described_class).to receive(:print)
12
+ allow_any_instance_of(described_class).to receive(:puts)
13
+ allow(ProgressBar).to receive(:create).and_return(progressbar_mock)
14
+ allow(progressbar_mock).to receive(:increment)
15
+ end
6
16
 
7
- let(:http_client) { double }
8
17
  let(:columns) { %w(email pass) }
9
18
 
10
19
  let(:success_response) do
@@ -14,12 +23,8 @@ describe RainforestCli::CSVImporter do
14
23
  }
15
24
  end
16
25
 
17
- before do
18
- RainforestCli::HttpClient.stub(:new).and_return(http_client)
19
- end
20
-
21
26
  it 'should post the schema to the generators API' do
22
- expect(http_client).to receive(:post)
27
+ expect_any_instance_of(RainforestCli::HttpClient).to receive(:post)
23
28
  .with('/generators', {
24
29
  name: 'variables',
25
30
  description: 'variables',
@@ -27,7 +32,7 @@ describe RainforestCli::CSVImporter do
27
32
  })
28
33
  .and_return success_response
29
34
 
30
- expect(http_client).to receive(:post)
35
+ expect_any_instance_of(RainforestCli::HttpClient).to receive(:post)
31
36
  .with('/generators/12345/rows', {
32
37
  data: {
33
38
  0 => 'russ@rainforestqa.com',
@@ -35,7 +40,7 @@ describe RainforestCli::CSVImporter do
35
40
  }
36
41
  }).and_return({})
37
42
 
38
- expect(http_client).to receive(:post)
43
+ expect_any_instance_of(RainforestCli::HttpClient).to receive(:post)
39
44
  .with('/generators/12345/rows', {
40
45
  data: {
41
46
  0 => 'bob@example.com',
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  describe RainforestCli::GitTrigger do
2
3
  subject { described_class }
3
4
 
4
5
  let(:default_dir) { __dir__ }
5
6
  let(:test_repo_dir) { File.join(Dir.tmpdir, 'raiforest-cli', 'test-repo') }
6
7
 
7
- describe ".last_commit_message" do
8
+ describe '.last_commit_message' do
8
9
  around(:each) do |spec|
9
10
  default_dir = Dir.pwd
10
11
  FileUtils.mkdir_p test_repo_dir
@@ -17,37 +18,49 @@ describe RainforestCli::GitTrigger do
17
18
  end
18
19
  end
19
20
 
20
- # Commented because it kills CI
21
- xit "returns a string" do
22
- expect(described_class.last_commit_message).to eq "Initial commit"
21
+ it 'returns a string' do
22
+ expect(described_class.last_commit_message).to eq 'Initial commit'
23
23
  end
24
24
  end
25
25
 
26
- describe ".git_trigger_should_run?" do
27
- it "returns true when @rainforest is in the string" do
26
+ describe '.git_trigger_should_run?' do
27
+ it 'returns true when @rainforest is in the string' do
28
28
  expect(described_class.git_trigger_should_run?('hello, world')).to eq false
29
29
  expect(described_class.git_trigger_should_run?('hello @rainforest')).to eq true
30
30
  end
31
31
  end
32
32
 
33
- describe ".extract_hashtags" do
34
- it "returns a list of hashtags" do
33
+ describe '.extract_hashtags' do
34
+ it 'returns a list of hashtags' do
35
35
  expect(described_class.extract_hashtags('hello, world')).to eq []
36
36
  expect(described_class.extract_hashtags('#hello, #world')).to eq []
37
37
  expect(described_class.extract_hashtags('@rainforest #hello, #world')).to eq ['hello', 'world']
38
38
  expect(described_class.extract_hashtags('#notForRainforest @rainforest #hello, #world')).to eq ['hello', 'world']
39
39
  expect(described_class.extract_hashtags('@rainforest #hello,#world')).to eq ['hello', 'world']
40
- expect(described_class.extract_hashtags('@rainforest #dashes-work, #underscores_work #007')).to eq ['dashes-work', 'underscores_work', '007']
40
+ expect(described_class.extract_hashtags('@rainforest #dashes-work, #underscores_work #007'))
41
+ .to eq ['dashes-work', 'underscores_work', '007']
41
42
  end
42
43
  end
43
44
 
44
45
  def setup_test_repo
45
- FileUtils.rm_rf File.join(test_repo_dir, "*")
46
- [
47
- "git init",
46
+ FileUtils.rm_rf File.join(test_repo_dir, '*')
47
+
48
+ commands = [
49
+ 'git init',
48
50
  "git commit --allow-empty -m 'Initial commit'",
49
- ].each do |cmd|
50
- system cmd
51
- end
51
+ ]
52
+
53
+ # Git must be set up each time on CircleCI
54
+ unless system 'git config --get user.email'
55
+ commands.unshift("git config --global user.email 'test@rainforestqa.com'")
56
+ end
57
+
58
+ unless system 'git config --get user.name'
59
+ commands.unshift("git config --global user.name 'Rainforest QA'")
60
+ end
61
+
62
+ commands.each do |cmd|
63
+ system cmd
64
+ end
52
65
  end
53
66
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ describe RainforestCli::HttpClient do
3
+ subject { described_class.new({ token: 'foo' }) }
4
+
5
+ describe '#get' do
6
+ describe 'maximum tolerated exceptions' do
7
+ let(:url) { 'http://some.url.com' }
8
+
9
+ before do
10
+ allow(HTTParty).to receive(:get).and_raise(SocketError)
11
+ end
12
+
13
+ context 'retries_on_failures omitted' do
14
+ it 'raises the error on the first exception' do
15
+ expect(HTTParty).to receive(:get).once
16
+ expect { subject.get(url, {}, retries_on_failures: false) }.to raise_error(Http::Exceptions::HttpException)
17
+ end
18
+ end
19
+
20
+ context 'retries_on_failures == false' do
21
+ 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)
24
+ end
25
+ end
26
+
27
+ context 'retries_on_failures == true' do
28
+ let(:response) { instance_double('HTTParty::Response', code: 200, body: {foo: :bar}.to_json) }
29
+ let(:delay_interval) { described_class::RETRY_INTERVAL }
30
+ subject { described_class.new({ token: 'foo' }).get(url, {}, retries_on_failures: true) }
31
+
32
+ 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
36
+ expect { subject }.to_not raise_error
37
+ end
38
+
39
+ 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
45
+ expect { subject }.to_not raise_error
46
+ end
47
+
48
+ 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
52
+ expect(subject).to eq(JSON.parse(response.body))
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
data/spec/options_spec.rb CHANGED
@@ -1,113 +1,107 @@
1
+ # frozen_string_literal: true
1
2
  describe RainforestCli::OptionParser do
2
3
  subject { RainforestCli::OptionParser.new(args) }
3
4
 
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" }
5
+ describe '#initialize' do
6
+ context 'importing csv file' do
7
+ let(:args) { ['--import-variable-csv-file', 'some_file.csv'] }
8
+
9
+ its(:import_file_name) { should == 'some_file.csv' }
8
10
  end
9
11
 
10
- context "test folder (when passed)" do
11
- let(:args) { ["--test-folder", "/path/to/folder"] }
12
- its(:test_folder) { should == "/path/to/folder" }
12
+ context 'test folder (when passed)' do
13
+ let(:args) { ['--test-folder', '/path/to/folder'] }
14
+ its(:test_folder) { should == '/path/to/folder' }
13
15
  end
14
16
 
15
- context "importing name" do
16
- let(:args) { ["--import-variable-name", "some_name"] }
17
- its(:import_name) { should == "some_name" }
17
+ context 'importing name' do
18
+ let(:args) { ['--import-variable-name', 'some_name'] }
19
+ its(:import_name) { should == 'some_name' }
18
20
  end
19
21
 
20
- context "run all tests" do
21
- let(:args) { ["run", "all"] }
22
- its(:tests) { should == ["all"]}
22
+ context 'run all tests' do
23
+ let(:args) { ['run', 'all'] }
24
+ its(:tests) { should == ['all']}
23
25
  its(:conflict) { should == nil}
24
26
  end
25
27
 
26
- context "run from tags" do
27
- let(:args) { ["run", "--tag", "run-me"] }
28
+ context 'run from tags' do
29
+ let(:args) { ['run', '--tag', 'run-me'] }
28
30
  its(:tests) { should == []}
29
- its(:tags) { should == ["run-me"]}
30
- end
31
-
32
- context "run from folder" do
33
- let(:args) { ["run", "--folder", "12"] }
34
- its(:folder) { should == "12"}
31
+ its(:tags) { should == ['run-me']}
35
32
  end
36
33
 
37
- context "only run in specific browsers" do
38
- let(:args) { ["run", "--browsers", "ie8"] }
39
- its(:browsers) { should == ["ie8"]}
34
+ context 'run from folder' do
35
+ let(:args) { ['run', '--folder', '12'] }
36
+ its(:folder) { should == '12'}
40
37
  end
41
38
 
42
- context "raises errors with invalid browsers" do
43
- let(:args) { ["run", "--browsers", "lulbrower"] }
44
-
45
- it "raises an exception" do
46
- expect{subject}.to raise_error(RainforestCli::BrowserException)
47
- end
39
+ context 'only run in specific browsers' do
40
+ let(:args) { ['run', '--browsers', 'ie8'] }
41
+ its(:browsers) { should == ['ie8']}
48
42
  end
49
43
 
50
- context "accepts multiple browsers" do
51
- let(:args) { ["run", "--browsers", "ie8,chrome"] }
52
- its(:browsers) { should == ["ie8", "chrome"]}
44
+ context 'accepts multiple browsers' do
45
+ let(:args) { ['run', '--browsers', 'ie8,chrome'] }
46
+ its(:browsers) { should == ['ie8', 'chrome']}
53
47
  end
54
48
 
55
- context "it parses the --git-trigger flag" do
56
- let(:args) { ["run", "--git-trigger", "all"] }
57
- its(:tests) { should == ["all"]}
58
- its(:git_trigger?) { should be_true }
49
+ context 'it parses the --git-trigger flag' do
50
+ let(:args) { ['run', '--git-trigger', 'all'] }
51
+ its(:tests) { should == ['all']}
52
+ its(:git_trigger?) { is_expected.to eq(true) }
59
53
  end
60
54
 
61
- context "it parses the --fg flag" do
62
- let(:args) { ["run", "--fg", "all"] }
63
- its(:tests) { should == ["all"]}
64
- its(:foreground?) { should be_true }
55
+ context 'it parses the --fg flag' do
56
+ let(:args) { ['run', '--fg', 'all'] }
57
+ its(:tests) { should == ['all']}
58
+ its(:foreground?) { is_expected.to eq(true) }
65
59
  end
66
60
 
67
- context "it parses the api token" do
68
- let(:args) { ["run", "--token", "abc", "all"] }
69
- its(:token) { should == "abc"}
61
+ context 'it parses the api token' do
62
+ let(:args) { ['run', '--token', 'abc', 'all'] }
63
+ its(:token) { should == 'abc'}
70
64
  end
71
65
 
72
- context "it parses the conflict flag" do
73
- context "when abort" do
74
- let(:args) { ["run", "--conflict", "abort", "all"] }
75
- its(:conflict) { should == "abort"}
66
+ context 'it parses the conflict flag' do
67
+ context 'when abort' do
68
+ let(:args) { ['run', '--conflict', 'abort', 'all'] }
69
+ its(:conflict) { should == 'abort'}
76
70
  end
77
71
 
78
- context "when abort-all" do
79
- let(:args) { ["run", "--conflict", "abort-all", "all"]}
80
- its(:conflict) { should == "abort-all" }
72
+ context 'when abort-all' do
73
+ let(:args) { ['run', '--conflict', 'abort-all', 'all']}
74
+ its(:conflict) { should == 'abort-all' }
81
75
  end
82
76
  end
83
77
 
84
- context "it parses the fail-fast flag" do
85
- let(:args) { ["run", "--fail-fast"] }
86
- its(:failfast?) { should be_true }
78
+ context 'it parses the fail-fast flag' do
79
+ let(:args) { ['run', '--fail-fast'] }
80
+ its(:failfast?) { is_expected.to eq(true) }
87
81
  end
88
82
 
89
- context "it parses the site-id flag" do
90
- let(:args) { ["run", "--site-id", '3'] }
83
+ context 'it parses the site-id flag' do
84
+ let(:args) { ['run', '--site-id', '3'] }
91
85
  its(:site_id) { should eq 3 }
92
86
  end
93
87
 
94
- context "it parses the environment-id flag" do
95
- let(:args) { ["run", "--environment-id", '3'] }
88
+ context 'it parses the environment-id flag' do
89
+ let(:args) { ['run', '--environment-id', '3'] }
96
90
  its(:environment_id) { should eq 3 }
97
91
  end
98
92
 
99
- context "it parses the custom-url flag" do
100
- let(:args) { ["run", "--custom-url", 'http://ad-hoc.example.com'] }
93
+ context 'it parses the custom-url flag' do
94
+ let(:args) { ['run', '--custom-url', 'http://ad-hoc.example.com'] }
101
95
  its(:custom_url) { should eq 'http://ad-hoc.example.com' }
102
96
  end
103
97
 
104
- context "it add a run description" do
105
- let(:args) { ["run", "--description", 'test description'] }
98
+ context 'it add a run description' do
99
+ let(:args) { ['run', '--description', 'test description'] }
106
100
  its(:description) { should eq 'test description' }
107
101
  end
108
102
  end
109
103
 
110
- describe "#validate!" do
104
+ describe '#validate!' do
111
105
  def does_not_raise_a_validation_exception
112
106
  expect do
113
107
  subject.validate!
@@ -120,36 +114,44 @@ describe RainforestCli::OptionParser do
120
114
  end.to raise_error(described_class::ValidationError)
121
115
  end
122
116
 
123
- context "with valid arguments" do
117
+ context 'with valid arguments' do
124
118
  let(:args) { %w(--token foo) }
125
119
  it { does_not_raise_a_validation_exception }
126
120
  end
127
121
 
128
- context "with missing token" do
122
+ context 'with missing token' do
129
123
  let(:args) { %w() }
130
124
  it { raises_a_validation_exception }
131
125
  end
132
126
 
133
- context "with a custom url but no site id" do
127
+ context 'with a custom url but no site id' do
134
128
  let(:args) { %w(--token foo --custom-url http://www.example.com) }
135
129
  it { raises_a_validation_exception }
136
130
  end
137
131
 
138
- context "with a import_file_name but no import name" do
132
+ context 'with a import_file_name but no import name' do
139
133
  let(:args) { %w(--token foo --import-variable-csv-file foo.csv) }
140
134
  it { raises_a_validation_exception }
141
135
  end
142
136
 
143
- context "with a import_file_name and a import_name" do
144
- context "for an existing file" do
137
+ context 'with a import_file_name and a import_name' do
138
+ context 'for an existing file' do
145
139
  let(:args) { %W(--token foo --import-variable-csv-file #{__FILE__} --import-variable-name my-var) }
146
140
  it { does_not_raise_a_validation_exception }
147
141
  end
148
142
 
149
- context "for a non existing file" do
143
+ context 'for a non existing file' do
150
144
  let(:args) { %W(--token foo --import-variable-csv-file does_not_exist --import-variable-name my-var) }
151
145
  it { raises_a_validation_exception }
152
146
  end
153
147
  end
148
+
149
+ context 'with invalid browsers' do
150
+ let(:args) { %w{run --browsers lulbrowser --token foo} }
151
+
152
+ it 'raises an exception' do
153
+ expect { subject.validate! }.to raise_error(RainforestCli::OptionParser::BrowserException)
154
+ end
155
+ end
154
156
  end
155
157
  end
@@ -1,6 +1,7 @@
1
1
  #! example_test (Test ID - only edit if this test has not yet been uploaded)
2
2
  # title: Example Test
3
- # start_uri: /
3
+ # tags: foo,bar,baz
4
+ # start_uri: /start_uri
4
5
 
5
6
  This is a step action.
6
7
  This is a question?
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ describe RainforestCli::RemoteTests do
3
+ subject { described_class.new('api_token') }
4
+
5
+ describe '#primary_key_dictionary' do
6
+ Test = Struct.new(:rfml_id, :id)
7
+
8
+ let(:test1) { Test.new('foo', 123) }
9
+ let(:test2) { Test.new('bar', 456) }
10
+ let(:test3) { Test.new('baz', 789) }
11
+ let(:tests) { [test1, test2, test3] }
12
+
13
+ before do
14
+ allow(subject).to receive(:tests).and_return(tests)
15
+ end
16
+
17
+ it "correctly formats the dictionary's keys and values" do
18
+ expect(subject.primary_key_dictionary)
19
+ .to include({'foo' => test1.id, 'bar' => test2.id, 'baz' => test3.id})
20
+ end
21
+ end
22
+ end
data/spec/runner_spec.rb CHANGED
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  describe RainforestCli::Runner do
2
3
  let(:args) { %w(run all) }
3
4
  let(:options) { RainforestCli::OptionParser.new(args) }
4
5
  subject { described_class.new(options) }
5
6
 
6
- describe "#get_environment_id" do
7
- context "with an invalid URL" do
7
+ describe '#get_environment_id' do
8
+ context 'with an invalid URL' do
8
9
  it 'errors out and exits' do
9
- expect {
10
+ expect do
10
11
  subject.get_environment_id('some=weird')
11
- }.to raise_error(SystemExit) { |error|
12
+ end.to raise_error(SystemExit) { |error|
12
13
  expect(error.status).to eq 2
13
14
  }
14
15
  end
@@ -16,27 +17,27 @@ describe RainforestCli::Runner do
16
17
 
17
18
  context 'on API error' do
18
19
  before do
19
- allow(subject.client).to receive(:post).and_return( {"error"=>"Some API error"} )
20
+ allow(subject.client).to receive(:post).and_return({'error'=>'Some API error'})
20
21
  end
21
22
 
22
23
  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 {
24
+ expect_any_instance_of(Logger).to receive(:fatal).with('Error creating the ad-hoc URL: Some API error')
25
+ expect do
25
26
  subject.get_environment_id('http://example.com')
26
- }.to raise_error(SystemExit) { |error|
27
+ end.to raise_error(SystemExit) { |error|
27
28
  expect(error.status).to eq 1
28
29
  }
29
30
  end
30
31
  end
31
32
  end
32
33
 
33
- describe "#url_valid?" do
34
+ describe '#url_valid?' do
34
35
  subject { super().url_valid?(url) }
35
36
  [
36
- "http://example.org",
37
- "https://example.org",
38
- "http://example.org/",
39
- "http://example.org?foo=bar",
37
+ 'http://example.org',
38
+ 'https://example.org',
39
+ 'http://example.org/',
40
+ 'http://example.org?foo=bar',
40
41
  ].each do |valid_url|
41
42
  context "#{valid_url}" do
42
43
  let(:url) { valid_url }
@@ -45,9 +46,9 @@ describe RainforestCli::Runner do
45
46
  end
46
47
 
47
48
  [
48
- "ftp://example.org",
49
- "example.org",
50
- "",
49
+ 'ftp://example.org',
50
+ 'example.org',
51
+ '',
51
52
  ].each do |valid_url|
52
53
  context "#{valid_url}" do
53
54
  let(:url) { valid_url }