rainforest-cli 1.6.5 → 1.7.0

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