rainforest-cli 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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 }