rainforest-cli 1.6.5 → 1.7.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +42 -7
  5. data/bin/rainforest +1 -1
  6. data/lib/{rainforest/cli.rb → rainforest_cli.rb} +23 -15
  7. data/lib/{rainforest/cli → rainforest_cli}/constants.rb +0 -0
  8. data/lib/{rainforest/cli → rainforest_cli}/csv_importer.rb +0 -0
  9. data/lib/{rainforest/cli → rainforest_cli}/deleter.rb +0 -0
  10. data/lib/{rainforest/cli → rainforest_cli}/exporter.rb +0 -0
  11. data/lib/{rainforest/cli → rainforest_cli}/git_trigger.rb +0 -0
  12. data/lib/{rainforest/cli → rainforest_cli}/http_client.rb +5 -4
  13. data/lib/rainforest_cli/junit_outputter.rb +69 -0
  14. data/lib/{rainforest/cli → rainforest_cli}/options.rb +40 -1
  15. data/lib/{rainforest/cli → rainforest_cli}/remote_tests.rb +0 -0
  16. data/lib/rainforest_cli/reporter.rb +64 -0
  17. data/lib/{rainforest/cli → rainforest_cli}/resources.rb +0 -0
  18. data/lib/{rainforest/cli → rainforest_cli}/runner.rb +20 -6
  19. data/lib/{rainforest/cli → rainforest_cli}/test_files.rb +0 -0
  20. data/lib/{rainforest/cli → rainforest_cli}/test_parser.rb +4 -65
  21. data/lib/rainforest_cli/test_parser/embedded_test.rb +14 -0
  22. data/lib/rainforest_cli/test_parser/step.rb +24 -0
  23. data/lib/rainforest_cli/test_parser/test.rb +40 -0
  24. data/lib/{rainforest/cli → rainforest_cli}/uploader.rb +32 -27
  25. data/lib/rainforest_cli/uploader/multi_form_post_request.rb +50 -0
  26. data/lib/rainforest_cli/uploader/uploadable_parser.rb +143 -0
  27. data/lib/{rainforest/cli → rainforest_cli}/validator.rb +0 -0
  28. data/lib/{rainforest/cli → rainforest_cli}/version.rb +1 -1
  29. data/rainforest-cli.gemspec +3 -1
  30. data/spec/fixtures/failed_test_response.json +29 -0
  31. data/spec/fixtures/runs_response.json +275 -0
  32. data/spec/fixtures/tests_response.json +130 -0
  33. data/spec/{csv_importer_spec.rb → rainforest_cli/csv_importer_spec.rb} +7 -14
  34. data/spec/{deleter_spec.rb → rainforest_cli/deleter_spec.rb} +0 -0
  35. data/spec/{exporter_spec.rb → rainforest_cli/exporter_spec.rb} +0 -0
  36. data/spec/{git_trigger_spec.rb → rainforest_cli/git_trigger_spec.rb} +0 -0
  37. data/spec/{http_client_spec.rb → rainforest_cli/http_client_spec.rb} +23 -0
  38. data/spec/rainforest_cli/junit_outputter_spec.rb +32 -0
  39. data/spec/{options_spec.rb → rainforest_cli/options_spec.rb} +33 -0
  40. data/spec/{remote_tests_spec.rb → rainforest_cli/remote_tests_spec.rb} +0 -0
  41. data/spec/rainforest_cli/reporter_spec.rb +51 -0
  42. data/spec/{resources_spec.rb → rainforest_cli/resources_spec.rb} +0 -0
  43. data/spec/{runner_spec.rb → rainforest_cli/runner_spec.rb} +0 -0
  44. data/spec/{test_files_spec.rb → rainforest_cli/test_files_spec.rb} +1 -1
  45. data/spec/rainforest_cli/test_parser/step_spec.rb +53 -0
  46. data/spec/rainforest_cli/test_parser_spec.rb +15 -0
  47. data/spec/rainforest_cli/uploader/uploadable_parser_spec.rb +84 -0
  48. data/spec/{uploader_spec.rb → rainforest_cli/uploader_spec.rb} +2 -2
  49. data/spec/{validator_spec.rb → rainforest_cli/validator_spec.rb} +6 -7
  50. data/spec/{cli_spec.rb → rainforest_cli_spec.rb} +5 -5
  51. data/spec/spec_helper.rb +4 -1
  52. metadata +95 -47
  53. data/.rvmrc +0 -1
  54. data/spec/test_parser_spec.rb +0 -95
File without changes
@@ -9,6 +9,11 @@ module RainforestCli
9
9
  end
10
10
 
11
11
  def run
12
+ if options.wait?
13
+ wait_for_run_completion(options.run_id)
14
+ return true
15
+ end
16
+
12
17
  if options.import_file_name && options.import_name
13
18
  delete_generator(options.import_name)
14
19
  CSVImporter.new(options.import_name, options.import_file_name, options.token).import
@@ -26,9 +31,20 @@ module RainforestCli
26
31
  exit 1
27
32
  end
28
33
 
34
+ run_id = response.fetch('id')
35
+ logger.info "Issued run #{run_id}"
36
+
29
37
  if options.foreground?
30
- run_id = response.fetch('id')
31
- wait_for_run_completion(run_id)
38
+ response = wait_for_run_completion(run_id)
39
+ if options.junit_file?
40
+ reporter = Reporter.new(options)
41
+ reporter.run_id = run_id
42
+ reporter.report
43
+ end
44
+
45
+ if response['result'] != 'passed'
46
+ exit 1
47
+ end
32
48
  else
33
49
  true
34
50
  end
@@ -37,7 +53,6 @@ module RainforestCli
37
53
  def wait_for_run_completion(run_id)
38
54
  running = true
39
55
  while running
40
- Kernel.sleep 5
41
56
  response = client.get("/runs/#{run_id}", {}, retries_on_failures: true)
42
57
  if response
43
58
  state_details = response.fetch('state_details')
@@ -50,15 +65,14 @@ module RainforestCli
50
65
  running = false
51
66
  end
52
67
  end
68
+ Kernel.sleep 5 if running
53
69
  end
54
70
 
55
71
  if response['frontend_url']
56
72
  logger.info "The detailed results are available at #{response['frontend_url']}"
57
73
  end
58
74
 
59
- if response['result'] != 'passed'
60
- exit 1
61
- end
75
+ return response
62
76
  end
63
77
 
64
78
  def make_create_run_options
@@ -1,70 +1,9 @@
1
1
  # frozen_string_literal: true
2
- module RainforestCli::TestParser
3
- class EmbeddedTest < Struct.new(:rfml_id, :redirect)
4
- def type
5
- :test
6
- end
7
-
8
- def to_s
9
- "--> embed: #{rfml_id}"
10
- end
11
-
12
- def redirection
13
- redirect || 'true'
14
- end
15
-
16
- def to_element(primary_key_id)
17
- {
18
- type: 'test',
19
- redirection: redirection,
20
- element: {
21
- id: primary_key_id
22
- }
23
- }
24
- end
25
- end
26
-
27
- class Step < Struct.new(:action, :response, :redirect)
28
- def type
29
- :step
30
- end
31
2
 
32
- def redirection
33
- redirect || 'true'
34
- end
35
-
36
- def to_s
37
- "#{action} --> #{response}"
38
- end
39
-
40
- def to_element
41
- {
42
- type: 'step',
43
- redirection: redirection,
44
- element: {
45
- action: action,
46
- response: response
47
- }
48
- }
49
- end
50
- end
51
-
52
- class Test < Struct.new(
53
- :file_name,
54
- :rfml_id,
55
- :description,
56
- :title,
57
- :start_uri,
58
- :site_id,
59
- :steps,
60
- :errors,
61
- :tags,
62
- :browsers
63
- )
64
- def embedded_ids
65
- steps.inject([]) { |embeds, step| step.type == :test ? embeds + [step.rfml_id] : embeds }
66
- end
67
- end
3
+ module RainforestCli::TestParser
4
+ require 'rainforest_cli/test_parser/test'
5
+ require 'rainforest_cli/test_parser/step'
6
+ require 'rainforest_cli/test_parser/embedded_test'
68
7
 
69
8
  class Error < Struct.new(:line, :reason)
70
9
  def to_s
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ class RainforestCli::TestParser::EmbeddedTest < Struct.new(:rfml_id, :redirect)
3
+ def type
4
+ :test
5
+ end
6
+
7
+ def to_s
8
+ "--> embed: #{rfml_id}"
9
+ end
10
+
11
+ def has_uploadable_files?
12
+ false
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ class RainforestCli::TestParser::Step < Struct.new(:action, :response, :redirect)
3
+ UPLOADABLE_REGEX = /{{ *file\.(download|screenshot)\(([^\)]+)\) *}}/
4
+
5
+ def type
6
+ :step
7
+ end
8
+
9
+ def to_s
10
+ "#{action} --> #{response}"
11
+ end
12
+
13
+ def has_uploadable_files?
14
+ uploadable_in_action.any? || uploadable_in_response.any?
15
+ end
16
+
17
+ def uploadable_in_action
18
+ action.scan(UPLOADABLE_REGEX)
19
+ end
20
+
21
+ def uploadable_in_response
22
+ response.scan(UPLOADABLE_REGEX)
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ class RainforestCli::TestParser::Test < Struct.new(
3
+ :file_name,
4
+ :rfml_id,
5
+ :description,
6
+ :title,
7
+ :start_uri,
8
+ :site_id,
9
+ :steps,
10
+ :errors,
11
+ :tags,
12
+ :browsers
13
+ )
14
+ def embedded_ids
15
+ steps.inject([]) { |embeds, step| step.type == :test ? embeds + [step.rfml_id] : embeds }
16
+ end
17
+
18
+ def to_json
19
+ {
20
+ start_uri: start_uri || '/',
21
+ title: title,
22
+ site_id: site_id,
23
+ description: description,
24
+ source: 'rainforest-cli',
25
+ tags: tags.uniq,
26
+ rfml_id: rfml_id,
27
+ browsers: browser_json,
28
+ }
29
+ end
30
+
31
+ def browser_json
32
+ browsers.map do |b|
33
+ {'state' => 'enabled', 'name' => b}
34
+ end
35
+ end
36
+
37
+ def has_uploadable_files?
38
+ steps.any?(&:has_uploadable_files?)
39
+ end
40
+ end
@@ -4,6 +4,8 @@ require 'parallel'
4
4
  require 'ruby-progressbar'
5
5
 
6
6
  class RainforestCli::Uploader
7
+ require 'rainforest_cli/uploader/uploadable_parser'
8
+
7
9
  attr_reader :test_files, :remote_tests, :validator
8
10
 
9
11
  def initialize(options)
@@ -26,6 +28,33 @@ class RainforestCli::Uploader
26
28
  each_in_parallel(rfml_tests) { |rfml_test| upload_test(rfml_test) }
27
29
  end
28
30
 
31
+ def create_test_obj(rfml_test)
32
+ parse_uploadables!(rfml_test) if rfml_test.has_uploadable_files?
33
+
34
+ elements = rfml_test.steps.map do |step|
35
+ element = case step.type
36
+ when :test then { id: primary_key_dictionary[step.rfml_id] }
37
+ when :step then { action: step.action, response: step.response }
38
+ else
39
+ logger.fatal "Unable to parse step type: #{step.type} in #{rfml_test.file_name}"
40
+ exit 1
41
+ end
42
+ {
43
+ type: step.type,
44
+ redirection: step.redirect || true,
45
+ element: element,
46
+ }
47
+ end
48
+
49
+ rfml_test.to_json.merge(elements: elements)
50
+ end
51
+
52
+ def parse_uploadables!(rfml_test)
53
+ test_id = primary_key_dictionary[rfml_test.rfml_id]
54
+ uploaded_files = http_client.get("/tests/#{test_id}/files")
55
+ UploadableParser.new(rfml_test, test_id, uploaded_files).parse_files!
56
+ end
57
+
29
58
  private
30
59
 
31
60
  def each_in_parallel(tests, &blk)
@@ -52,7 +81,7 @@ class RainforestCli::Uploader
52
81
  title: rfml_test.title,
53
82
  start_uri: rfml_test.start_uri,
54
83
  rfml_id: rfml_test.rfml_id,
55
- source: 'rainforest-cli'
84
+ source: 'rainforest-cli',
56
85
  }
57
86
  rf_test = Rainforest::Test.create(test_obj)
58
87
 
@@ -79,31 +108,7 @@ class RainforestCli::Uploader
79
108
  RainforestCli.logger
80
109
  end
81
110
 
82
- def create_test_obj(rfml_test)
83
- test_obj = {
84
- start_uri: rfml_test.start_uri || '/',
85
- title: rfml_test.title,
86
- site_id: rfml_test.site_id,
87
- description: rfml_test.description,
88
- source: 'rainforest-cli',
89
- tags: rfml_test.tags.uniq,
90
- rfml_id: rfml_test.rfml_id
91
- }
92
-
93
- test_obj[:elements] = rfml_test.steps.map do |step|
94
- if step.respond_to?(:rfml_id)
95
- step.to_element(primary_key_dictionary[step.rfml_id])
96
- else
97
- step.to_element
98
- end
99
- end
100
-
101
- unless rfml_test.browsers.empty?
102
- test_obj[:browsers] = rfml_test.browsers.map do|b|
103
- {'state' => 'enabled', 'name' => b}
104
- end
105
- end
106
-
107
- test_obj
111
+ def http_client
112
+ RainforestCli.http_client
108
113
  end
109
114
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require 'httparty'
3
+
4
+ class RainforestCli::Uploader::MultiFormPostRequest
5
+ BOUNDARY = 'RainforestCli'
6
+
7
+ class Param < Struct.new(:param_name, :value)
8
+ def to_multipart
9
+ <<-EOS
10
+ Content-Disposition: form-data; name="#{param_name}"\r
11
+ \r
12
+ #{value}\r
13
+ EOS
14
+ end
15
+ end
16
+
17
+ class FileParam < Struct.new(:param_name, :file)
18
+ def to_multipart
19
+ <<-EOS
20
+ Content-Disposition: form-data; name="#{param_name}"; filename="#{File.basename(file.path)}"\r
21
+ Content-Type: #{MimeMagic.by_path(file.path)}\r
22
+ Content-Transfer-Encoding: binary\r
23
+ \r
24
+ #{file.read.strip}\r
25
+ EOS
26
+ end
27
+ end
28
+
29
+ class << self
30
+ def request(url, params)
31
+ HTTParty.post(url, body: make_body(params), headers: headers)
32
+ end
33
+
34
+ def make_body(params)
35
+ fp = []
36
+ params.each do |k, v|
37
+ if v.respond_to?(:read)
38
+ fp.push(FileParam.new(k, v))
39
+ else
40
+ fp.push(Param.new(k, v))
41
+ end
42
+ end
43
+ fp.map { |p| "--#{BOUNDARY}\n#{p.to_multipart}" }.join + "--#{BOUNDARY}--"
44
+ end
45
+
46
+ def headers
47
+ { 'Content-type' => "multipart/form-data, boundary=#{BOUNDARY}" }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+ require 'mimemagic'
3
+ require 'rainforest_cli/uploader/multi_form_post_request'
4
+
5
+ class RainforestCli::Uploader::UploadableParser
6
+ def initialize(rfml_test, test_id, uploaded_files)
7
+ @rfml_test = rfml_test
8
+ @test_id = test_id
9
+ @uploaded_files = uploaded_files
10
+ end
11
+
12
+ def parse_files!
13
+ @rfml_test.steps.each do |step|
14
+ next if step.type == :test
15
+ parse_action_files(step) if step.uploadable_in_action
16
+ parse_response_files(step) if step.uploadable_in_response
17
+ end
18
+ end
19
+
20
+ def parse_action_files(step)
21
+ step.uploadable_in_action.each do |match|
22
+ step.action = replace_paths_in_text(step.action, match)
23
+ end
24
+ end
25
+
26
+ def parse_response_files(step)
27
+ step.uploadable_in_response.each do |match|
28
+ step.response = replace_paths_in_text(step.response, match)
29
+ end
30
+ end
31
+
32
+ def replace_paths_in_text(text, match)
33
+ step_var, relative_file_path = match
34
+ file_path = File.expand_path(File.join(test_directory, relative_file_path))
35
+
36
+ unless File.exist?(file_path)
37
+ logger.warn "\tError for test: #{@rfml_test.file_name}:"
38
+ logger.warn "\t\tNo such file exists: #{File.basename(file_path)}"
39
+ return text
40
+ end
41
+
42
+ file = File.open(file_path, 'rb')
43
+ aws_info = get_aws_upload_info(file)
44
+
45
+ sig = aws_info['file_signature'][0...6]
46
+ if step_var == 'screenshot'
47
+ text.gsub(relative_file_path, "#{aws_info['file_id']}, #{sig}")
48
+ elsif step_var == 'download'
49
+ text.gsub(relative_file_path, "#{aws_info['file_id']}, #{sig}, #{File.basename(file_path)}")
50
+ end
51
+ end
52
+
53
+ def get_aws_upload_info(file)
54
+ if file_already_uploaded?(file)
55
+ aws_info = get_uploaded_data(file)
56
+ else
57
+ aws_info = upload_to_rainforest(file)
58
+ add_file_to_uploaded_collection(file, aws_info)
59
+ upload_to_aws(file, aws_info)
60
+ end
61
+
62
+ aws_info
63
+ end
64
+
65
+ def upload_to_rainforest(file)
66
+ logger.info "\tUploading file metadata for #{file.path}..."
67
+
68
+ resp = http_client.post(
69
+ "/tests/#{@test_id}/files",
70
+ mime_type: MimeMagic.by_path(file).to_s,
71
+ size: file.size,
72
+ name: File.basename(file.path),
73
+ digest: file_digest(file)
74
+ )
75
+
76
+ if resp['aws_url'].nil?
77
+ logger.fatal "\tThere was a problem with uploading your file: #{file_path}."
78
+ logger.fatal "\t\t#{resp.to_json}"
79
+ exit 2
80
+ end
81
+
82
+ resp
83
+ end
84
+
85
+ def upload_to_aws(file, aws_info)
86
+ logger.info "\tUploading file data for #{file.path}..."
87
+
88
+ resp = RainforestCli::Uploader::MultiFormPostRequest.request(
89
+ aws_info['aws_url'],
90
+ 'key' => aws_info['aws_key'],
91
+ 'AWSAccessKeyId' => aws_info['aws_access_id'],
92
+ 'acl' => aws_info['aws_acl'],
93
+ 'policy' => aws_info['aws_policy'],
94
+ 'signature' => aws_info['aws_signature'],
95
+ 'Content-Type' => MimeMagic.by_path(file),
96
+ 'file' => file,
97
+ )
98
+
99
+ unless resp.code.between?(200, 299)
100
+ logger.fatal "\tThere was a problem with uploading your file: #{file.path}."
101
+ logger.fatal "\t\t#{resp.to_json}"
102
+ exit 3
103
+ end
104
+ end
105
+
106
+ def add_file_to_uploaded_collection(file, aws_info)
107
+ @uploaded_files.push({
108
+ 'id' => aws_info['file_id'],
109
+ 'signature' => aws_info['file_signature'],
110
+ 'digest' => file_digest(file),
111
+ })
112
+ end
113
+
114
+ def file_already_uploaded?(file)
115
+ @uploaded_files.any? { |f| f['digest'] == file_digest(file) }
116
+ end
117
+
118
+ def get_uploaded_data(file)
119
+ file_data = @uploaded_files.find { |f| f['digest'] == file_digest(file) }
120
+ {
121
+ 'file_signature' => file_data['signature'],
122
+ 'file_id' => file_data['id'],
123
+ }
124
+ end
125
+
126
+ private
127
+
128
+ def file_digest(file)
129
+ Digest::MD5.file(file).hexdigest
130
+ end
131
+
132
+ def test_directory
133
+ @test_directory ||= File.dirname(@rfml_test.file_name)
134
+ end
135
+
136
+ def http_client
137
+ RainforestCli.http_client
138
+ end
139
+
140
+ def logger
141
+ RainforestCli.logger
142
+ end
143
+ end