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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +80 -0
- data/.rubocop.yml +9 -70
- data/.rubocop_todo.yml +68 -0
- data/Gemfile +5 -4
- data/README.md +69 -0
- data/active_encode.gemspec +12 -3
- data/app/controllers/active_encode/encode_record_controller.rb +1 -0
- data/app/jobs/active_encode/polling_job.rb +1 -1
- data/app/models/active_encode/encode_record.rb +1 -0
- data/db/migrate/20180822021048_create_active_encode_encode_records.rb +1 -0
- data/db/migrate/20190702153755_add_create_options_to_active_encode_encode_records.rb +6 -0
- data/db/migrate/20190712174821_add_progress_to_active_encode_encode_records.rb +6 -0
- data/lib/active_encode.rb +1 -0
- data/lib/active_encode/base.rb +2 -2
- data/lib/active_encode/callbacks.rb +1 -0
- data/lib/active_encode/core.rb +4 -3
- data/lib/active_encode/engine.rb +1 -0
- data/lib/active_encode/engine_adapter.rb +1 -0
- data/lib/active_encode/engine_adapters.rb +4 -1
- data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +31 -29
- data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +138 -87
- data/lib/active_encode/engine_adapters/matterhorn_adapter.rb +5 -4
- data/lib/active_encode/engine_adapters/media_convert_adapter.rb +399 -0
- data/lib/active_encode/engine_adapters/media_convert_output.rb +104 -0
- data/lib/active_encode/engine_adapters/pass_through_adapter.rb +239 -0
- data/lib/active_encode/engine_adapters/test_adapter.rb +5 -4
- data/lib/active_encode/engine_adapters/zencoder_adapter.rb +3 -2
- data/lib/active_encode/errors.rb +6 -0
- data/lib/active_encode/global_id.rb +2 -1
- data/lib/active_encode/input.rb +3 -2
- data/lib/active_encode/output.rb +3 -2
- data/lib/active_encode/persistence.rb +11 -5
- data/lib/active_encode/polling.rb +3 -2
- data/lib/active_encode/spec/shared_specs.rb +2 -0
- data/{spec/shared_specs/engine_adapter_specs.rb → lib/active_encode/spec/shared_specs/engine_adapter.rb} +37 -38
- data/lib/active_encode/status.rb +1 -0
- data/lib/active_encode/technical_metadata.rb +3 -2
- data/lib/active_encode/version.rb +2 -1
- data/lib/file_locator.rb +8 -9
- data/spec/controllers/encode_record_controller_spec.rb +4 -3
- data/spec/fixtures/ffmpeg/cancelled-id/cancelled +0 -0
- data/spec/fixtures/file with space.low.mp4 +0 -0
- data/spec/fixtures/file with space.mp4 +0 -0
- data/spec/fixtures/fireworks.low.mp4 +0 -0
- data/spec/fixtures/media_convert/endpoints.json +1 -0
- data/spec/fixtures/media_convert/job_canceled.json +412 -0
- data/spec/fixtures/media_convert/job_canceling.json +1 -0
- data/spec/fixtures/media_convert/job_completed.json +359 -0
- data/spec/fixtures/media_convert/job_completed_detail.json +1 -0
- data/spec/fixtures/media_convert/job_completed_detail_query.json +1 -0
- data/spec/fixtures/media_convert/job_completed_empty_detail.json +1 -0
- data/spec/fixtures/media_convert/job_created.json +408 -0
- data/spec/fixtures/media_convert/job_failed.json +406 -0
- data/spec/fixtures/media_convert/job_progressing.json +414 -0
- data/spec/fixtures/pass_through/cancelled-id/cancelled +0 -0
- data/spec/fixtures/pass_through/cancelled-id/input_metadata +90 -0
- data/spec/fixtures/pass_through/completed-id/completed +0 -0
- data/spec/fixtures/pass_through/completed-id/input_metadata +102 -0
- data/spec/fixtures/pass_through/completed-id/output_metadata-high +90 -0
- data/spec/fixtures/pass_through/completed-id/output_metadata-low +90 -0
- data/spec/fixtures/pass_through/completed-id/video-high.mp4 +0 -0
- data/spec/fixtures/pass_through/completed-id/video-low.mp4 +0 -0
- data/spec/fixtures/pass_through/failed-id/error.log +1 -0
- data/spec/fixtures/pass_through/failed-id/input_metadata +90 -0
- data/spec/fixtures/pass_through/running-id/input_metadata +90 -0
- data/spec/integration/elastic_transcoder_adapter_spec.rb +30 -30
- data/spec/integration/ffmpeg_adapter_spec.rb +93 -25
- data/spec/integration/matterhorn_adapter_spec.rb +45 -44
- data/spec/integration/media_convert_adapter_spec.rb +152 -0
- data/spec/integration/pass_through_adapter_spec.rb +151 -0
- data/spec/integration/zencoder_adapter_spec.rb +210 -209
- data/spec/rails_helper.rb +1 -0
- data/spec/routing/encode_record_controller_routing_spec.rb +1 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -12
- data/spec/units/callbacks_spec.rb +3 -2
- data/spec/units/core_spec.rb +26 -25
- data/spec/units/engine_adapter_spec.rb +1 -0
- data/spec/units/file_locator_spec.rb +20 -19
- data/spec/units/global_id_spec.rb +12 -11
- data/spec/units/input_spec.rb +8 -5
- data/spec/units/output_spec.rb +8 -5
- data/spec/units/persistence_spec.rb +15 -11
- data/spec/units/polling_job_spec.rb +7 -6
- data/spec/units/polling_spec.rb +1 -0
- data/spec/units/status_spec.rb +3 -3
- metadata +158 -14
- 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,
|
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,
|
11
|
+
def find(id, _opts = {})
|
11
12
|
build_encode(get_job_details(id))
|
12
13
|
end
|
13
14
|
|
@@ -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(
|
13
|
+
def to_global_id(_options = {})
|
13
14
|
super(app: 'ActiveEncode')
|
14
15
|
end
|
15
16
|
end
|
data/lib/active_encode/input.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
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
|
data/lib/active_encode/output.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
12
|
+
:after_status_update, :after_failed, :after_cancelled, :after_completed
|
12
13
|
].freeze
|
13
14
|
|
14
15
|
included do
|
@@ -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
|
-
|
24
|
+
it { expect(subject.id).to be_present }
|
25
25
|
it { is_expected.to be_running }
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
53
|
+
it { expect(subject.id).to be_present }
|
54
54
|
it { is_expected.to be_running }
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
80
|
+
it { expect(subject.id).to be_present }
|
81
81
|
it { is_expected.to be_cancelled }
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
107
|
+
it { expect(subject.id).to be_present }
|
108
108
|
it { is_expected.to be_completed }
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
145
|
+
it { expect(subject.id).to be_present }
|
146
146
|
it { is_expected.to be_failed }
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
177
|
+
it { expect(subject.id).to eq cancelling_job.id }
|
178
178
|
it { is_expected.to be_cancelled }
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
205
|
-
it {
|
206
|
-
|
207
|
-
|
208
|
-
|
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
|