active_encode 0.5.0 → 0.8.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +80 -0
  3. data/.rubocop.yml +9 -70
  4. data/.rubocop_todo.yml +68 -0
  5. data/Gemfile +5 -4
  6. data/README.md +69 -0
  7. data/active_encode.gemspec +12 -3
  8. data/app/controllers/active_encode/encode_record_controller.rb +1 -0
  9. data/app/jobs/active_encode/polling_job.rb +1 -1
  10. data/app/models/active_encode/encode_record.rb +1 -0
  11. data/db/migrate/20180822021048_create_active_encode_encode_records.rb +1 -0
  12. data/db/migrate/20190702153755_add_create_options_to_active_encode_encode_records.rb +6 -0
  13. data/db/migrate/20190712174821_add_progress_to_active_encode_encode_records.rb +6 -0
  14. data/lib/active_encode.rb +1 -0
  15. data/lib/active_encode/base.rb +2 -2
  16. data/lib/active_encode/callbacks.rb +1 -0
  17. data/lib/active_encode/core.rb +4 -3
  18. data/lib/active_encode/engine.rb +1 -0
  19. data/lib/active_encode/engine_adapter.rb +1 -0
  20. data/lib/active_encode/engine_adapters.rb +4 -1
  21. data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +31 -29
  22. data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +138 -87
  23. data/lib/active_encode/engine_adapters/matterhorn_adapter.rb +5 -4
  24. data/lib/active_encode/engine_adapters/media_convert_adapter.rb +399 -0
  25. data/lib/active_encode/engine_adapters/media_convert_output.rb +104 -0
  26. data/lib/active_encode/engine_adapters/pass_through_adapter.rb +239 -0
  27. data/lib/active_encode/engine_adapters/test_adapter.rb +5 -4
  28. data/lib/active_encode/engine_adapters/zencoder_adapter.rb +3 -2
  29. data/lib/active_encode/errors.rb +6 -0
  30. data/lib/active_encode/global_id.rb +2 -1
  31. data/lib/active_encode/input.rb +3 -2
  32. data/lib/active_encode/output.rb +3 -2
  33. data/lib/active_encode/persistence.rb +11 -5
  34. data/lib/active_encode/polling.rb +3 -2
  35. data/lib/active_encode/spec/shared_specs.rb +2 -0
  36. data/{spec/shared_specs/engine_adapter_specs.rb → lib/active_encode/spec/shared_specs/engine_adapter.rb} +37 -38
  37. data/lib/active_encode/status.rb +1 -0
  38. data/lib/active_encode/technical_metadata.rb +3 -2
  39. data/lib/active_encode/version.rb +2 -1
  40. data/lib/file_locator.rb +8 -9
  41. data/spec/controllers/encode_record_controller_spec.rb +4 -3
  42. data/spec/fixtures/ffmpeg/cancelled-id/cancelled +0 -0
  43. data/spec/fixtures/file with space.low.mp4 +0 -0
  44. data/spec/fixtures/file with space.mp4 +0 -0
  45. data/spec/fixtures/fireworks.low.mp4 +0 -0
  46. data/spec/fixtures/media_convert/endpoints.json +1 -0
  47. data/spec/fixtures/media_convert/job_canceled.json +412 -0
  48. data/spec/fixtures/media_convert/job_canceling.json +1 -0
  49. data/spec/fixtures/media_convert/job_completed.json +359 -0
  50. data/spec/fixtures/media_convert/job_completed_detail.json +1 -0
  51. data/spec/fixtures/media_convert/job_completed_detail_query.json +1 -0
  52. data/spec/fixtures/media_convert/job_completed_empty_detail.json +1 -0
  53. data/spec/fixtures/media_convert/job_created.json +408 -0
  54. data/spec/fixtures/media_convert/job_failed.json +406 -0
  55. data/spec/fixtures/media_convert/job_progressing.json +414 -0
  56. data/spec/fixtures/pass_through/cancelled-id/cancelled +0 -0
  57. data/spec/fixtures/pass_through/cancelled-id/input_metadata +90 -0
  58. data/spec/fixtures/pass_through/completed-id/completed +0 -0
  59. data/spec/fixtures/pass_through/completed-id/input_metadata +102 -0
  60. data/spec/fixtures/pass_through/completed-id/output_metadata-high +90 -0
  61. data/spec/fixtures/pass_through/completed-id/output_metadata-low +90 -0
  62. data/spec/fixtures/pass_through/completed-id/video-high.mp4 +0 -0
  63. data/spec/fixtures/pass_through/completed-id/video-low.mp4 +0 -0
  64. data/spec/fixtures/pass_through/failed-id/error.log +1 -0
  65. data/spec/fixtures/pass_through/failed-id/input_metadata +90 -0
  66. data/spec/fixtures/pass_through/running-id/input_metadata +90 -0
  67. data/spec/integration/elastic_transcoder_adapter_spec.rb +30 -30
  68. data/spec/integration/ffmpeg_adapter_spec.rb +93 -25
  69. data/spec/integration/matterhorn_adapter_spec.rb +45 -44
  70. data/spec/integration/media_convert_adapter_spec.rb +152 -0
  71. data/spec/integration/pass_through_adapter_spec.rb +151 -0
  72. data/spec/integration/zencoder_adapter_spec.rb +210 -209
  73. data/spec/rails_helper.rb +1 -0
  74. data/spec/routing/encode_record_controller_routing_spec.rb +1 -0
  75. data/spec/spec_helper.rb +2 -2
  76. data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -12
  77. data/spec/units/callbacks_spec.rb +3 -2
  78. data/spec/units/core_spec.rb +26 -25
  79. data/spec/units/engine_adapter_spec.rb +1 -0
  80. data/spec/units/file_locator_spec.rb +20 -19
  81. data/spec/units/global_id_spec.rb +12 -11
  82. data/spec/units/input_spec.rb +8 -5
  83. data/spec/units/output_spec.rb +8 -5
  84. data/spec/units/persistence_spec.rb +15 -11
  85. data/spec/units/polling_job_spec.rb +7 -6
  86. data/spec/units/polling_spec.rb +1 -0
  87. data/spec/units/status_spec.rb +3 -3
  88. metadata +158 -14
  89. data/.travis.yml +0 -19
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'nokogiri'
4
+ require 'shellwords'
5
+ require 'file_locator'
6
+
7
+ # PassThroughAdapter accepts an input file url and a number of derivative urls in the options
8
+ # E.g. `create(input, outputs: [{ label: 'low', url: 'file:///derivatives/low.mp4' }, { label: 'high', url: 'file:///derivatives/high.mp4' }])`
9
+ # This adapter mirrors the ffmpeg adapter but differs in a few ways:
10
+ # 1. It starts by copying the derivative files to the work directory
11
+ # 2. It runs Mediainfo on the input and output files and skips ffmpeg
12
+ # 3. All work is done in the create method so it's status is always completed or failed
13
+ module ActiveEncode
14
+ module EngineAdapters
15
+ class PassThroughAdapter
16
+ WORK_DIR = ENV["ENCODE_WORK_DIR"] || "encodes" # Should read from config
17
+ MEDIAINFO_PATH = ENV["MEDIAINFO_PATH"] || "mediainfo"
18
+
19
+ def create(input_url, options = {})
20
+ # Decode file uris for ffmpeg (mediainfo works either way)
21
+ input_url = URI.decode(input_url) if input_url.starts_with? "file:///"
22
+
23
+ new_encode = ActiveEncode::Base.new(input_url, options)
24
+ new_encode.id = SecureRandom.uuid
25
+ new_encode.current_operations = []
26
+ new_encode.output = []
27
+
28
+ # Create a working directory that holds all output files related to the encode
29
+ FileUtils.mkdir_p working_path("", new_encode.id)
30
+ FileUtils.mkdir_p working_path("outputs", new_encode.id)
31
+
32
+ # Extract technical metadata from input file
33
+ `#{MEDIAINFO_PATH} --Output=XML --LogFile=#{working_path("input_metadata", new_encode.id)} #{input_url.shellescape}`
34
+ new_encode.input = build_input new_encode
35
+ new_encode.input.id = new_encode.id
36
+ new_encode.created_at, new_encode.updated_at = get_times new_encode.id
37
+
38
+ if new_encode.input.duration.blank?
39
+ new_encode.state = :failed
40
+ new_encode.percent_complete = 1
41
+
42
+ new_encode.errors = if new_encode.input.file_size.blank?
43
+ ["#{input_url} does not exist or is not accessible"]
44
+ else
45
+ ["Error inspecting input: #{input_url}"]
46
+ end
47
+
48
+ write_errors new_encode
49
+ return new_encode
50
+ end
51
+
52
+ # For saving filename to label map used to find the label when building outputs
53
+ filename_label_hash = {}
54
+
55
+ # Copy derivatives to work directory
56
+ options[:outputs].each do |opt|
57
+ url = opt[:url]
58
+ output_path = working_path("outputs/#{sanitize_base opt[:url]}#{File.extname opt[:url]}", new_encode.id)
59
+ FileUtils.cp FileLocator.new(url).location, output_path
60
+ filename_label_hash[output_path] = opt[:label]
61
+ end
62
+
63
+ # Write filename-to-label map so we can retrieve them on build_output
64
+ File.write working_path("filename_label.yml", new_encode.id), filename_label_hash.to_yaml
65
+
66
+ new_encode.percent_complete = 1
67
+ new_encode.state = :running
68
+ new_encode.errors = []
69
+
70
+ new_encode
71
+ rescue StandardError => e
72
+ new_encode.state = :failed
73
+ new_encode.percent_complete = 1
74
+ new_encode.errors = [e.full_message]
75
+ write_errors new_encode
76
+ return new_encode
77
+ end
78
+
79
+ # Return encode object from file system
80
+ def find(id, opts = {})
81
+ encode_class = opts[:cast]
82
+ encode_class ||= ActiveEncode::Base
83
+ encode = encode_class.new(nil, opts)
84
+ encode.id = id
85
+ encode.created_at, encode.updated_at = get_times encode.id
86
+ encode.input = build_input encode
87
+ encode.input.id = encode.id
88
+ encode.output = []
89
+ encode.current_operations = []
90
+
91
+ encode.errors = read_errors(id)
92
+ if encode.errors.present?
93
+ encode.state = :failed
94
+ encode.percent_complete = 1
95
+ elsif cancelled?(id)
96
+ encode.state = :cancelled
97
+ encode.percent_complete = 1
98
+ elsif completed?(id)
99
+ encode.state = :completed
100
+ encode.percent_complete = 100
101
+ else
102
+ encode.output = build_outputs encode
103
+ encode.state = :completed
104
+ encode.percent_complete = 100
105
+ end
106
+
107
+ encode
108
+ rescue StandardError => e
109
+ encode.state = :failed
110
+ encode.percent_complete = 1
111
+ encode.errors = [e.full_message]
112
+ write_errors encode
113
+ return encode
114
+ end
115
+
116
+ # Cancel ongoing encode using pid file
117
+ def cancel(id)
118
+ # Check for errors and if not then create cancelled file else raise CancelError?
119
+ if running?(id)
120
+ File.write(working_path("cancelled", id), "")
121
+ find id
122
+ else
123
+ raise CancelError
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def running?(id)
130
+ !cancelled?(id) || !failed?(id) || !completed?(id)
131
+ end
132
+
133
+ def cancelled?(id)
134
+ File.exist? working_path("cancelled", id)
135
+ end
136
+
137
+ def failed?(id)
138
+ read_errors(id).present?
139
+ end
140
+
141
+ def completed?(id)
142
+ File.exist? working_path("completed", id)
143
+ end
144
+
145
+ def get_times(id)
146
+ updated_at = if File.file? working_path("completed", id)
147
+ File.mtime(working_path("completed", id))
148
+ elsif File.file? working_path("cancelled", id)
149
+ File.mtime(working_path("cancelled", id))
150
+ elsif File.file? working_path("error.log", id)
151
+ File.mtime(working_path("error.log", id))
152
+ else
153
+ File.mtime(working_path("input_metadata", id))
154
+ end
155
+
156
+ [File.mtime(working_path("input_metadata", id)), updated_at]
157
+ end
158
+
159
+ def write_errors(encode)
160
+ File.write(working_path("error.log", encode.id), encode.errors.join("\n"))
161
+ end
162
+
163
+ def read_errors(id)
164
+ err_path = working_path("error.log", id)
165
+ error = File.read(err_path) if File.file? err_path
166
+ if error.present?
167
+ [error]
168
+ else
169
+ []
170
+ end
171
+ end
172
+
173
+ def build_input(encode)
174
+ input = ActiveEncode::Input.new
175
+ metadata = get_tech_metadata(working_path("input_metadata", encode.id))
176
+ input.url = metadata[:url]
177
+ input.assign_tech_metadata(metadata)
178
+ created_at = File.mtime(working_path("input_metadata", encode.id))
179
+ input.created_at = created_at
180
+ input.updated_at = created_at
181
+
182
+ input
183
+ end
184
+
185
+ def build_outputs(encode)
186
+ id = encode.id
187
+ outputs = []
188
+ filename_label_hash = YAML.safe_load(File.read(working_path("filename_label.yml", id))) if File.exist?(working_path("filename_label.yml", id))
189
+ Dir["#{File.absolute_path(working_path('outputs', id))}/*"].each do |file_path|
190
+ output = ActiveEncode::Output.new
191
+ output.url = "file://#{file_path}"
192
+ output.label = filename_label_hash[file_path] if filename_label_hash
193
+ output.id = "#{encode.input.id}-#{output.label}"
194
+ output.created_at = encode.created_at
195
+ output.updated_at = File.mtime file_path
196
+
197
+ # Extract technical metadata from output file
198
+ metadata_path = working_path("output_metadata-#{output.label}", id)
199
+ `#{MEDIAINFO_PATH} --Output=XML --LogFile=#{metadata_path} #{output.url}` unless File.file? metadata_path
200
+ output.assign_tech_metadata(get_tech_metadata(metadata_path))
201
+
202
+ outputs << output
203
+ end
204
+ File.write(working_path("completed", id), "")
205
+
206
+ outputs
207
+ end
208
+
209
+ def sanitize_base(input_url)
210
+ File.basename(input_url, File.extname(input_url)).gsub(/[^0-9A-Za-z.\-]/, '_')
211
+ end
212
+
213
+ def working_path(path, id)
214
+ File.join(WORK_DIR, id, path)
215
+ end
216
+
217
+ def get_tech_metadata(file_path)
218
+ doc = Nokogiri::XML File.read(file_path)
219
+ doc.remove_namespaces!
220
+ duration = get_xpath_text(doc, '//Duration/text()', :to_f)
221
+ duration *= 1000 unless duration.nil? # Convert to milliseconds
222
+ { url: get_xpath_text(doc, '//media/@ref', :to_s),
223
+ width: get_xpath_text(doc, '//Width/text()', :to_f),
224
+ height: get_xpath_text(doc, '//Height/text()', :to_f),
225
+ frame_rate: get_xpath_text(doc, '//FrameRate/text()', :to_f),
226
+ duration: duration,
227
+ file_size: get_xpath_text(doc, '//FileSize/text()', :to_i),
228
+ audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
229
+ audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
230
+ video_codec: get_xpath_text(doc, '//track[@type="Video"]/CodecID/text()', :to_s),
231
+ video_bitrate: get_xpath_text(doc, '//track[@type="Video"]/BitRate/text()', :to_i) }
232
+ end
233
+
234
+ def get_xpath_text(doc, xpath, cast_method)
235
+ doc.xpath(xpath).first&.text&.send(cast_method)
236
+ end
237
+ end
238
+ end
239
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ActiveEncode
2
3
  module EngineAdapters
3
4
  class TestAdapter
@@ -9,8 +10,8 @@ module ActiveEncode
9
10
  new_encode = ActiveEncode::Base.new(input_url, options)
10
11
  new_encode.id = SecureRandom.uuid
11
12
  new_encode.state = :running
12
- new_encode.created_at = Time.now
13
- new_encode.updated_at = Time.now
13
+ new_encode.created_at = Time.now.utc
14
+ new_encode.updated_at = Time.now.utc
14
15
  @encodes[new_encode.id] = new_encode
15
16
  new_encode
16
17
  end
@@ -18,14 +19,14 @@ module ActiveEncode
18
19
  def find(id, _opts = {})
19
20
  new_encode = @encodes[id]
20
21
  # Update the updated_at time to simulate changes
21
- new_encode.updated_at = Time.now
22
+ new_encode.updated_at = Time.now.utc
22
23
  new_encode
23
24
  end
24
25
 
25
26
  def cancel(id)
26
27
  new_encode = @encodes[id]
27
28
  new_encode.state = :cancelled
28
- new_encode.updated_at = Time.now
29
+ new_encode.updated_at = Time.now.utc
29
30
  new_encode
30
31
  end
31
32
  end
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
1
2
  module ActiveEncode
2
3
  module EngineAdapters
3
4
  class ZencoderAdapter
4
5
  # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input.url into a zencoder accepted url
5
- def create(input_url, options = {})
6
+ def create(input_url, _options = {})
6
7
  response = Zencoder::Job.create(input: input_url.to_s)
7
8
  build_encode(get_job_details(response.body["id"]))
8
9
  end
9
10
 
10
- def find(id, opts = {})
11
+ def find(id, _opts = {})
11
12
  build_encode(get_job_details(id))
12
13
  end
13
14
 
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module ActiveEncode #:nodoc:
3
+ class NotFound < RuntimeError; end
4
+ class NotRunningError < RuntimeError; end
5
+ class CancelError < RuntimeError; end
6
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'globalid'
2
3
 
3
4
  module ActiveEncode
@@ -9,7 +10,7 @@ module ActiveEncode
9
10
  other.is_a?(ActiveEncode::Base) && to_global_id == other.to_global_id
10
11
  end
11
12
 
12
- def to_global_id(options = {})
13
+ def to_global_id(_options = {})
13
14
  super(app: 'ActiveEncode')
14
15
  end
15
16
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ActiveEncode
2
3
  class Input
3
4
  include Status
@@ -8,8 +9,8 @@ module ActiveEncode
8
9
 
9
10
  def valid?
10
11
  id.present? && url.present? &&
11
- created_at.is_a?(Time) && updated_at.is_a?(Time) &&
12
- updated_at >= created_at
12
+ created_at.is_a?(Time) && updated_at.is_a?(Time) &&
13
+ updated_at >= created_at
13
14
  end
14
15
  end
15
16
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ActiveEncode
2
3
  class Output
3
4
  include Status
@@ -9,8 +10,8 @@ module ActiveEncode
9
10
 
10
11
  def valid?
11
12
  id.present? && url.present? && label.present? &&
12
- created_at.is_a?(Time) && updated_at.is_a?(Time) &&
13
- updated_at >= created_at
13
+ created_at.is_a?(Time) && updated_at.is_a?(Time) &&
14
+ updated_at >= created_at
14
15
  end
15
16
  end
16
17
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'active_support'
2
3
 
3
4
  module ActiveEncode
@@ -9,8 +10,10 @@ module ActiveEncode
9
10
  persist(persistence_model_attributes(encode))
10
11
  end
11
12
 
12
- after_create do |encode|
13
- persist(persistence_model_attributes(encode))
13
+ around_create do |encode, block|
14
+ create_options = encode.options
15
+ encode = block.call
16
+ persist(persistence_model_attributes(encode, create_options))
14
17
  end
15
18
 
16
19
  after_cancel do |encode|
@@ -29,8 +32,8 @@ module ActiveEncode
29
32
  model.update(encode_attributes) # Don't fail if persisting doesn't succeed?
30
33
  end
31
34
 
32
- def persistence_model_attributes(encode)
33
- {
35
+ def persistence_model_attributes(encode, create_options = nil)
36
+ attributes = {
34
37
  global_id: encode.to_global_id.to_s,
35
38
  state: encode.state,
36
39
  adapter: encode.class.engine_adapter.class.name,
@@ -38,8 +41,11 @@ module ActiveEncode
38
41
  # Need to ensure that these values come through or else validations will fail
39
42
  created_at: encode.created_at,
40
43
  updated_at: encode.updated_at,
41
- raw_object: encode.to_json
44
+ raw_object: encode.to_json,
45
+ progress: encode.percent_complete
42
46
  }
47
+ attributes[:create_options] = create_options.to_json if create_options.present?
48
+ attributes
43
49
  end
44
50
  end
45
51
  end
@@ -1,4 +1,5 @@
1
- require 'active_support'
1
+ # frozen_string_literal: true
2
+ require 'active_support/core_ext/integer/time'
2
3
  require 'active_model/callbacks'
3
4
 
4
5
  module ActiveEncode
@@ -8,7 +9,7 @@ module ActiveEncode
8
9
  POLLING_WAIT_TIME = 10.seconds.freeze
9
10
 
10
11
  CALLBACKS = [
11
- :after_status_update, :after_failed, :after_cancelled, :after_completed
12
+ :after_status_update, :after_failed, :after_cancelled, :after_completed
12
13
  ].freeze
13
14
 
14
15
  included do
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require 'active_encode/spec/shared_specs/engine_adapter.rb'
@@ -21,13 +21,13 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
21
21
  it 'returns an ActiveEncode::Base object' do
22
22
  expect(subject.class).to be ActiveEncode::Base
23
23
  end
24
- its(:id) { is_expected.not_to be_empty }
24
+ it { expect(subject.id).to be_present }
25
25
  it { is_expected.to be_running }
26
- its(:current_operations) { is_expected.to be_empty }
27
- its(:percent_complete) { is_expected.to be < 100 }
28
- its(:errors) { is_expected.to be_empty }
29
- its(:created_at) { is_expected.to be_kind_of Time }
30
- its(:updated_at) { is_expected.to be_kind_of Time }
26
+ it { expect(subject.current_operations).to be_empty }
27
+ it { expect(subject.percent_complete).to be < 100 }
28
+ it { expect(subject.errors).to be_empty }
29
+ it { expect(subject.created_at).to be_kind_of Time }
30
+ it { expect(subject.updated_at).to be_kind_of Time }
31
31
 
32
32
  it 'input is a valid ActiveEncode::Input object' do
33
33
  expect(subject.input).to be_a ActiveEncode::Input
@@ -50,12 +50,12 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
50
50
  it 'returns an ActiveEncode::Base object' do
51
51
  expect(subject.class).to be ActiveEncode::Base
52
52
  end
53
- its(:id) { is_expected.to eq 'running-id' }
53
+ it { expect(subject.id).to be_present }
54
54
  it { is_expected.to be_running }
55
- its(:percent_complete) { is_expected.to be > 0 }
56
- its(:errors) { is_expected.to be_empty }
57
- its(:created_at) { is_expected.to be_kind_of Time }
58
- its(:updated_at) { is_expected.to be > subject.created_at }
55
+ it { expect(subject.percent_complete).to be_positive }
56
+ it { expect(subject.errors).to be_empty }
57
+ it { expect(subject.created_at).to be_kind_of Time }
58
+ it { expect(subject.updated_at).to be >= subject.created_at }
59
59
 
60
60
  it 'input is a valid ActiveEncode::Input object' do
61
61
  expect(subject.input).to be_a ActiveEncode::Input
@@ -77,12 +77,12 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
77
77
  it 'returns an ActiveEncode::Base object' do
78
78
  expect(subject.class).to be ActiveEncode::Base
79
79
  end
80
- its(:id) { is_expected.to eq 'cancelled-id' }
80
+ it { expect(subject.id).to be_present }
81
81
  it { is_expected.to be_cancelled }
82
- its(:percent_complete) { is_expected.to be > 0 }
83
- its(:errors) { is_expected.to be_empty }
84
- its(:created_at) { is_expected.to be_kind_of Time }
85
- its(:updated_at) { is_expected.to be >= subject.created_at }
82
+ it { expect(subject.percent_complete).to be_positive }
83
+ it { expect(subject.errors).to be_empty }
84
+ it { expect(subject.created_at).to be_kind_of Time }
85
+ it { expect(subject.updated_at).to be >= subject.created_at }
86
86
 
87
87
  it 'input is a valid ActiveEncode::Input object' do
88
88
  expect(subject.input).to be_a ActiveEncode::Input
@@ -104,12 +104,12 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
104
104
  it 'returns an ActiveEncode::Base object' do
105
105
  expect(subject.class).to be ActiveEncode::Base
106
106
  end
107
- its(:id) { is_expected.to eq 'completed-id' }
107
+ it { expect(subject.id).to be_present }
108
108
  it { is_expected.to be_completed }
109
- its(:percent_complete) { is_expected.to eq 100 }
110
- its(:errors) { is_expected.to be_empty }
111
- its(:created_at) { is_expected.to be_kind_of Time }
112
- its(:updated_at) { is_expected.to be > subject.created_at }
109
+ it { expect(subject.percent_complete).to eq 100 }
110
+ it { expect(subject.errors).to be_empty }
111
+ it { expect(subject.created_at).to be_kind_of Time }
112
+ it { expect(subject.updated_at).to be > subject.created_at }
113
113
 
114
114
  it 'input is a valid ActiveEncode::Input object' do
115
115
  expect(subject.input).to be_a ActiveEncode::Input
@@ -130,7 +130,7 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
130
130
 
131
131
  it 'output has technical metadata' do
132
132
  subject.output.each do |output|
133
- expected_output = completed_output.find {|expected_out| expected_out[:id] == output.id }
133
+ expected_output = completed_output.find { |expected_out| expected_out[:id] == output.id }
134
134
  expect(output.as_json.symbolize_keys).to include expected_output
135
135
  end
136
136
  end
@@ -142,12 +142,12 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
142
142
  it 'returns an ActiveEncode::Base object' do
143
143
  expect(subject.class).to be ActiveEncode::Base
144
144
  end
145
- its(:id) { is_expected.to eq 'failed-id' }
145
+ it { expect(subject.id).to be_present }
146
146
  it { is_expected.to be_failed }
147
- its(:percent_complete) { is_expected.to be > 0 }
148
- its(:errors) { is_expected.not_to be_empty }
149
- its(:created_at) { is_expected.to be_kind_of Time }
150
- its(:updated_at) { is_expected.to be > subject.created_at }
147
+ it { expect(subject.percent_complete).to be_positive }
148
+ it { expect(subject.errors).not_to be_empty }
149
+ it { expect(subject.created_at).to be_kind_of Time }
150
+ it { expect(subject.updated_at).to be > subject.created_at }
151
151
 
152
152
  it 'input is a valid ActiveEncode::Input object' do
153
153
  expect(subject.input).to be_a ActiveEncode::Input
@@ -174,12 +174,12 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
174
174
  it 'returns an ActiveEncode::Base object' do
175
175
  expect(subject.class).to be ActiveEncode::Base
176
176
  end
177
- its(:id) { is_expected.to eq cancelling_job.id }
177
+ it { expect(subject.id).to eq cancelling_job.id }
178
178
  it { is_expected.to be_cancelled }
179
- its(:percent_complete) { is_expected.to be > 0 }
180
- its(:errors) { is_expected.to be_empty }
181
- its(:created_at) { is_expected.to be_kind_of Time }
182
- its(:updated_at) { is_expected.to be >= subject.created_at }
179
+ it { expect(subject.percent_complete).to be_positive }
180
+ it { expect(subject.errors).to be_empty }
181
+ it { expect(subject.created_at).to be_kind_of Time }
182
+ it { expect(subject.updated_at).to be >= subject.created_at }
183
183
 
184
184
  it 'input is a valid ActiveEncode::Input object' do
185
185
  expect(subject.input).to be_a ActiveEncode::Input
@@ -201,12 +201,11 @@ RSpec.shared_examples 'an ActiveEncode::EngineAdapter' do |*_flags|
201
201
  it 'returns an ActiveEncode::Base object' do
202
202
  expect(subject.class).to be ActiveEncode::Base
203
203
  end
204
- its(:id) { is_expected.to eq 'running-id' }
205
- it { is_expected.to be_running }
206
- its(:percent_complete) { is_expected.to be > 0 }
207
- its(:errors) { is_expected.to be_empty }
208
- its(:created_at) { is_expected.to be_kind_of Time }
209
- its(:updated_at) { is_expected.to be > subject.created_at }
204
+ it { expect(subject.id).to be_present }
205
+ it { expect(subject.percent_complete).to be_positive }
206
+ it { expect(subject.errors).to be_empty }
207
+ it { expect(subject.created_at).to be_kind_of Time }
208
+ it { expect(subject.updated_at).to be >= subject.created_at }
210
209
 
211
210
  it 'input is a valid ActiveEncode::Input object' do
212
211
  expect(subject.input).to be_a ActiveEncode::Input