resque-integration 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +28 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/Appraisals +27 -0
  6. data/CHANGELOG.md +311 -0
  7. data/Gemfile +4 -0
  8. data/README.md +281 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/standalone/progress_bar.js +47 -0
  11. data/app/controllers/resque/jobs_controller.rb +42 -0
  12. data/app/controllers/resque/queues/info_controller.rb +9 -0
  13. data/app/controllers/resque/queues/status_controller.rb +38 -0
  14. data/app/views/shared/job_progress_bar.html.haml +17 -0
  15. data/bin/resque-status +59 -0
  16. data/config/routes.rb +13 -0
  17. data/config.ru +9 -0
  18. data/dip.yml +44 -0
  19. data/docker-compose.development.yml +12 -0
  20. data/docker-compose.drone.yml +6 -0
  21. data/docker-compose.yml +10 -0
  22. data/lib/generators/resque/integration/install/install_generator.rb +38 -0
  23. data/lib/resque/integration/backtrace.rb +29 -0
  24. data/lib/resque/integration/configuration.rb +238 -0
  25. data/lib/resque/integration/continuous.rb +75 -0
  26. data/lib/resque/integration/engine.rb +103 -0
  27. data/lib/resque/integration/extensions/job.rb +17 -0
  28. data/lib/resque/integration/extensions/worker.rb +17 -0
  29. data/lib/resque/integration/extensions.rb +8 -0
  30. data/lib/resque/integration/failure_backends/queues_totals.rb +37 -0
  31. data/lib/resque/integration/failure_backends.rb +7 -0
  32. data/lib/resque/integration/god.erb +99 -0
  33. data/lib/resque/integration/hooks.rb +72 -0
  34. data/lib/resque/integration/logs_rotator.rb +95 -0
  35. data/lib/resque/integration/monkey_patch/verbose_formatter.rb +10 -0
  36. data/lib/resque/integration/ordered.rb +142 -0
  37. data/lib/resque/integration/priority.rb +89 -0
  38. data/lib/resque/integration/queues_info/age.rb +53 -0
  39. data/lib/resque/integration/queues_info/config.rb +96 -0
  40. data/lib/resque/integration/queues_info/size.rb +33 -0
  41. data/lib/resque/integration/queues_info.rb +55 -0
  42. data/lib/resque/integration/tasks/hooks.rake +49 -0
  43. data/lib/resque/integration/tasks/lock.rake +37 -0
  44. data/lib/resque/integration/tasks/resque.rake +101 -0
  45. data/lib/resque/integration/unique.rb +218 -0
  46. data/lib/resque/integration/version.rb +5 -0
  47. data/lib/resque/integration.rb +146 -0
  48. data/lib/resque-integration.rb +1 -0
  49. data/resque-integration.gemspec +40 -0
  50. data/spec/fixtures/resque_queues.yml +45 -0
  51. data/spec/resque/controllers/jobs_controller_spec.rb +65 -0
  52. data/spec/resque/integration/configuration_spec.rb +147 -0
  53. data/spec/resque/integration/continuous_spec.rb +122 -0
  54. data/spec/resque/integration/failure_backends/queues_totals_spec.rb +105 -0
  55. data/spec/resque/integration/ordered_spec.rb +87 -0
  56. data/spec/resque/integration/priority_spec.rb +94 -0
  57. data/spec/resque/integration/queues_info_spec.rb +402 -0
  58. data/spec/resque/integration/unique_spec.rb +184 -0
  59. data/spec/resque/integration_spec.rb +105 -0
  60. data/spec/shared/resque_inline.rb +10 -0
  61. data/spec/spec_helper.rb +28 -0
  62. data/vendor/assets/images/progressbar/white.gif +0 -0
  63. data/vendor/assets/javascripts/jquery.progressbar.js +177 -0
  64. data/vendor/assets/stylesheets/jquery.progressbar.css.erb +33 -0
  65. data/vendor/assets/stylesheets/jquery.progressbar.no_pipeline.css +33 -0
  66. metadata +402 -0
@@ -0,0 +1,101 @@
1
+ namespace :resque do
2
+ desc 'Generate God configuration file'
3
+ task :conf => :environment do
4
+ File.write(Resque.config.config_file, Resque.config.to_god)
5
+
6
+ puts "God configuration file generated to #{Resque.config.config_file}"
7
+ end
8
+
9
+ desc 'Start God server and watch for Resque workers'
10
+ task :start => :conf do
11
+ if god_running?
12
+ puts `#{god} start resque`
13
+ else
14
+ puts `#{god} -c #{Resque.config.config_file} -P #{Resque.config.pid_file} --log #{Resque.config.god_log_file} --log-level #{Resque.config.god_log_level} --no-syslog`
15
+ end
16
+ end
17
+
18
+ desc 'Restart Resque workers'
19
+ task :restart => :conf do
20
+ if god_stopped?
21
+ Rake::Task['resque:start'].invoke
22
+ else
23
+ puts `#{god} load #{Resque.config.config_file} stop && #{god} restart resque`
24
+ end
25
+ end
26
+
27
+ desc 'Stop Resque workers'
28
+ task :stop do
29
+ puts `#{god} stop resque`
30
+ end
31
+
32
+ desc 'Stop Resque workers and quit God'
33
+ task :terminate do
34
+ puts `#{god} terminate`
35
+ end
36
+
37
+ desc 'Stop processing any new jobs'
38
+ task :pause do
39
+ puts `#{god} signal resque USR2`
40
+ end
41
+
42
+ desc 'Resume jobs processing after pause'
43
+ task :resume do
44
+ puts `#{god} signal resque CONT`
45
+ end
46
+
47
+ desc 'Shows Resque status'
48
+ task :status do
49
+ puts `#{god} status resque`
50
+ end
51
+
52
+ desc 'Установить время истечения resque:resque-retry:*'
53
+ task :expire => :environment do
54
+ cursor = 0
55
+ redis = Redis.current
56
+
57
+ loop do
58
+ cursor, keys = redis.scan(cursor, count: 10_000, match: 'resque:resque-retry:*')
59
+ cursor = cursor.to_i
60
+
61
+ unless keys.empty?
62
+ redis.pipelined do
63
+ keys.each { |key| redis.expire(key, 1.hour.seconds) }
64
+ end
65
+ end
66
+
67
+ break if cursor.zero?
68
+ end
69
+ end
70
+
71
+ namespace :logs do
72
+ desc 'Rotate resque logs'
73
+ task :rotate => :environment do
74
+ if god_running?
75
+ Process.kill('USR1', File.read(Resque.config.pid_file).to_i)
76
+ sleep 3
77
+ puts `#{god} signal resque HUP`
78
+ else
79
+ puts 'god is not running'
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def god
87
+ `which god`.strip
88
+ end
89
+
90
+ def god_running?
91
+ File.exists?(Resque.config.pid_file) && Process.kill(0, File.read(Resque.config.pid_file).to_i)
92
+ rescue Errno::ESRCH
93
+ false
94
+ rescue Errno::EPERM
95
+ true
96
+ end
97
+
98
+ def god_stopped?
99
+ !god_running?
100
+ end
101
+ end
@@ -0,0 +1,218 @@
1
+ require 'digest/sha1'
2
+ require 'active_support/core_ext/module/aliasing'
3
+ require 'active_support/core_ext/hash'
4
+ require 'resque/plugins/progress'
5
+
6
+ module Resque
7
+ module Integration
8
+ # Unique job
9
+ #
10
+ # @example
11
+ # class MyJob
12
+ # include Resque::Integration
13
+ #
14
+ # # jobs are considered as equal if their first argument is the same
15
+ # unique { |*args| args.first }
16
+ #
17
+ # def self.execute(image_id)
18
+ # # do it
19
+ # end
20
+ # end
21
+ #
22
+ # MyJob.enqueue(11)
23
+ module Unique
24
+ LOCK_TIMEOUT = 259_200 # 3 days
25
+
26
+ def self.extended(base)
27
+ if base.singleton_class.include?(::Resque::Integration::Priority)
28
+ raise 'Uniqueness should be enabled before Prioritness'
29
+ end
30
+
31
+ base.extend(::Resque::Plugins::Progress)
32
+ base.singleton_class.prepend(Overrides)
33
+ end
34
+
35
+ module Overrides
36
+ # Overriding +enqueue+ method here so now it returns existing metadata if job already queued
37
+ def enqueue(*args) #:nodoc:
38
+ meta = enqueued?(*args)
39
+ return meta if meta
40
+
41
+ # enqueue job and retrieve its meta
42
+ super
43
+ end
44
+
45
+ # Overriding +meta_id+ here so now it generates the same MetaID for Jobs with same args
46
+ def meta_id(*args)
47
+ ::Digest::SHA1.hexdigest([secret_token, self, lock_on.call(*args)].join)
48
+ end
49
+ end
50
+
51
+ # Returns true because job is unique now
52
+ def unique?
53
+ true
54
+ end
55
+
56
+ # Метод вызывает resque-scheduler чтобы поставить задание в текущую очередь
57
+ def scheduled(queue, klass, *args)
58
+ klass.constantize.enqueue_to(queue, *args)
59
+ end
60
+
61
+ # Метод вызывает resque-retry когда ставить отложенное задание
62
+ # здесь мы убираем meta_id из аргументов
63
+ def retry_args(meta_id, *args)
64
+ args
65
+ end
66
+
67
+ # Метод вызывает resque-retry, когда записывает/читает число перезапусков
68
+ # - во время работы воркера первым аргументом передается meta_id;
69
+ # - во время чтения из вебинтерфейса, meta_id не передается, т.к. она выкидывается во время перепостановки
70
+ # джоба(см retry_args);
71
+ # - если метод вызывается в пользовательском коде(и @meta_id отсутствует), то meta_id нельзя передавать.
72
+ def retry_identifier(*args)
73
+ return if args.empty?
74
+ args.shift if @meta_id.is_a?(String) && !@meta_id.empty? && @meta_id == args.first
75
+ lock_id(*args)
76
+ end
77
+
78
+ # Get or set proc returning unique arguments
79
+ def lock_on(&block)
80
+ if block_given?
81
+ @unique = block
82
+ else
83
+ @unique ||= proc { |*args| args }
84
+ end
85
+ end
86
+
87
+ # LockID should be independent from MetaID
88
+ # @api private
89
+ def lock_id(*args)
90
+ args = args.map { |i| i.is_a?(Hash) ? i.with_indifferent_access : i }
91
+ locked_args = lock_on.call(*args)
92
+ encoded_args = ::Digest::SHA1.hexdigest(obj_to_string(locked_args))
93
+ "lock:#{name}-#{encoded_args}"
94
+ end
95
+
96
+ # get meta object associated with job
97
+ def meta
98
+ get_meta(@meta_id)
99
+ end
100
+
101
+ # default `perform` method override
102
+ def perform(meta_id, *args)
103
+ execute(*args)
104
+ end
105
+
106
+ def execute(*)
107
+ raise NotImplementedError, "You should implement `execute' method"
108
+ end
109
+
110
+ # When job is failed we should remove lock
111
+ def on_failure_lock(_e, _meta_id, *args)
112
+ unlock(*args)
113
+ end
114
+
115
+ # Before dequeue check if job is running
116
+ def before_dequeue_lock(*args)
117
+ (meta_id = args.first) &&
118
+ (meta = get_meta(meta_id)) &&
119
+ !meta.working?
120
+ end
121
+
122
+ def on_failure_retry(exception, *args)
123
+ return unless defined?(super)
124
+
125
+ # Keep meta_id if kill -9 (or ABRT)
126
+ @meta_id = args.first if exception.is_a?(::Resque::DirtyExit)
127
+
128
+ super
129
+ end
130
+
131
+ # Before enqueue acquire a lock
132
+ #
133
+ # Returns boolean
134
+ def before_enqueue_lock(_meta_id, *args)
135
+ ::Resque.redis.set(lock_id(*args), 1, ex: lock_timeout, nx: true)
136
+ end
137
+
138
+ def around_perform_lock(_meta_id, *args)
139
+ yield
140
+ ensure
141
+ # Always clear the lock when we're done, even if there is an error.
142
+ unlock(*args)
143
+ end
144
+
145
+ # When job is dequeued we should remove lock
146
+ def after_dequeue_lock(_meta_id, *args)
147
+ unlock(*args) unless args.empty?
148
+ end
149
+
150
+ # Fail metadata if dequeue succeed
151
+ def after_dequeue_meta(*args)
152
+ if (meta_id = args.first) && (meta = get_meta(meta_id))
153
+ meta.fail!
154
+ end
155
+ end
156
+
157
+ # Is job already in queue or in process?
158
+ def enqueued?(*args)
159
+ # if lock exists and timeout not exceeded
160
+ get_meta(meta_id(*args)) if locked?(*args)
161
+ end
162
+
163
+ def lock_timeout
164
+ LOCK_TIMEOUT
165
+ end
166
+
167
+ # Returns true if resque job is in locked state
168
+ def locked?(*args)
169
+ ::Resque.redis.exists(lock_id(*args))
170
+ end
171
+
172
+ # Dequeue unique job
173
+ def dequeue(*args)
174
+ ::Resque.dequeue(self, meta_id(*args), *args)
175
+ end
176
+
177
+ def enqueue_to(queue, *args)
178
+ meta = enqueued?(*args)
179
+ return meta if meta.present?
180
+
181
+ meta = ::Resque::Plugins::Meta::Metadata.new('meta_id' => meta_id(args), 'job_class' => to_s)
182
+ meta.save
183
+
184
+ ::Resque.enqueue_to(queue, self, meta.meta_id, *args)
185
+ meta
186
+ end
187
+
188
+ private
189
+
190
+ # Remove lock for job with given +args+
191
+ def unlock(*args)
192
+ ::Resque.redis.del(lock_id(*args))
193
+ end
194
+
195
+ def secret_token
196
+ ::Rails.respond_to?(:application) &&
197
+ ::Rails.application &&
198
+ ::Rails.application.config.secret_token
199
+ end
200
+
201
+ def obj_to_string(obj)
202
+ case obj
203
+ when Hash
204
+ s = []
205
+ obj.keys.sort.each do |k|
206
+ s << obj_to_string(k)
207
+ s << obj_to_string(obj[k])
208
+ end
209
+ s.to_s
210
+ when Array
211
+ obj.map { |a| obj_to_string(a) }.to_s
212
+ else
213
+ obj.to_s
214
+ end
215
+ end
216
+ end # module Unique
217
+ end # module Integration
218
+ end # module Resque
@@ -0,0 +1,5 @@
1
+ module Resque
2
+ module Integration
3
+ VERSION = '3.4.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,146 @@
1
+ require 'resque/integration/version'
2
+
3
+ require 'active_support/all'
4
+ require 'rails/railtie'
5
+ require 'active_record'
6
+ require 'action_pack'
7
+
8
+ require 'rake'
9
+ require 'multi_json'
10
+
11
+ require 'resque'
12
+ silence_warnings { require 'resque/plugins/meta' }
13
+
14
+ require 'resque/integration/monkey_patch/verbose_formatter'
15
+ require 'resque/integration/hooks'
16
+
17
+ require 'resque/scheduler'
18
+ require 'resque/scheduler/tasks'
19
+ require 'resque-retry'
20
+
21
+ module Resque
22
+ include Integration::Hooks
23
+ extend Integration::Hooks
24
+
25
+ # Resque.config is available now
26
+ mattr_accessor :config
27
+
28
+ def queues_info
29
+ return @queues_info if defined?(@queues_info)
30
+
31
+ queues_info_config = Rails.root.join('config', 'resque_queues.yml')
32
+
33
+ @queues_info = Resque::Integration::QueuesInfo.new(config: queues_info_config)
34
+ end
35
+
36
+ # Seamless resque integration with all necessary plugins
37
+ # You should define an +execute+ method (not +perform+)
38
+ #
39
+ # Usage:
40
+ # class MyJob
41
+ # include Resque::Integration
42
+ #
43
+ # queue :my_queue
44
+ # unique ->(*args) { args.first }
45
+
46
+ # def self.execute(*args)
47
+ # end
48
+ # end
49
+ module Integration
50
+ autoload :Backtrace, 'resque/integration/backtrace'
51
+ autoload :CLI, 'resque/integration/cli'
52
+ autoload :Configuration, 'resque/integration/configuration'
53
+ autoload :Continuous, 'resque/integration/continuous'
54
+ autoload :Unique, 'resque/integration/unique'
55
+ autoload :Ordered, 'resque/integration/ordered'
56
+ autoload :LogsRotator, 'resque/integration/logs_rotator'
57
+ autoload :QueuesInfo, 'resque/integration/queues_info'
58
+ autoload :Extensions, 'resque/integration/extensions'
59
+ autoload :FailureBackends, 'resque/integration/failure_backends'
60
+ autoload :Priority, 'resque/integration/priority'
61
+
62
+ extend ActiveSupport::Concern
63
+
64
+ included do
65
+ extend Backtrace
66
+
67
+ @queue ||= :default
68
+ end
69
+
70
+ module ClassMethods
71
+ # Get or set queue name (just a synonym to resque native methodology)
72
+ def queue(name = nil)
73
+ if name
74
+ @queue = name
75
+ else
76
+ @queue
77
+ end
78
+ end
79
+
80
+ # Mark Job as unique and set given +callback+ or +block+ as Unique Arguments procedure
81
+ def unique(callback = nil, &block)
82
+ extend Unique unless unique?
83
+
84
+ lock_on(&(callback || block))
85
+ end
86
+
87
+ # Extend job with 'continuous' functionality so you can re-enqueue job with +continue+ method.
88
+ def continuous
89
+ extend Continuous
90
+ end
91
+
92
+ def unique?
93
+ false
94
+ end
95
+
96
+ # Public: job used priority queues
97
+ def priority?
98
+ false
99
+ end
100
+
101
+ # Extend resque-retry.
102
+ #
103
+ # options - Hash
104
+ #
105
+ # :limit - Integer (default: 2)
106
+ # :delay - Integer (default: 60)
107
+ # :expire_retry_key_after - Integer (default: 3200), истечение ключа в секундах. Если
108
+ #
109
+ # t - среднее время выполнения одного джоба
110
+ # n - текущее кол-во джобов в очереди
111
+ # k - кол-во воркеров
112
+ #
113
+ # то expire_retry_key_after >= t * n / k
114
+ #
115
+ # Иначе ключ истечет, прежде чем джоб отработает.
116
+ #
117
+ #
118
+ # Returns nothing
119
+ def retrys(options = {})
120
+ if unique?
121
+ raise '`retrys` should be declared higher in code than `unique`'
122
+ end
123
+
124
+ extend Resque::Plugins::Retry
125
+
126
+ @retry_limit = options.fetch(:limit, 2)
127
+ @retry_delay = options.fetch(:delay, 60)
128
+ @expire_retry_key_after = options.fetch(:expire_retry_key_after, 1.hour.seconds)
129
+ end
130
+
131
+ # Mark Job as ordered
132
+ def ordered(options = {})
133
+ extend Ordered
134
+
135
+ self.max_iterations = options.fetch(:max_iterations, 20)
136
+ self.uniqueness = Ordered::Uniqueness.new(&options[:unique]) if options.key?(:unique)
137
+ end
138
+
139
+ def prioritized
140
+ extend Priority
141
+ end
142
+ end
143
+ end # module Integration
144
+ end # module Resque
145
+
146
+ require 'resque/integration/engine'
@@ -0,0 +1 @@
1
+ require 'resque/integration'
@@ -0,0 +1,40 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'resque/integration/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'resque-integration'
7
+ gem.version = Resque::Integration::VERSION
8
+ gem.authors = ['Alexei Mikhailov', 'Michail Merkushin']
9
+ gem.email = %w(amikhailov83@gmail.com merkushin.m.s@gmail.com)
10
+ gem.summary = %q{Seamless integration of resque with resque-progress and resque-lock}
11
+ gem.homepage = 'https://github.com/abak-press/resque-integration'
12
+
13
+ gem.files = `git ls-files`.split($/)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = %w(lib)
17
+
18
+ gem.add_runtime_dependency 'resque', '>= 1.25.2'
19
+ gem.add_runtime_dependency 'rails', '>= 3.0.0'
20
+ gem.add_runtime_dependency 'resque-meta', '>= 2.0.0'
21
+ gem.add_runtime_dependency 'resque-progress', '~> 1.0.1'
22
+ gem.add_runtime_dependency 'resque-multi-job-forks', '~> 0.4.2'
23
+ gem.add_runtime_dependency 'resque-failed-job-mailer', '~> 0.0.3'
24
+ gem.add_runtime_dependency 'resque-scheduler', '~> 4.0'
25
+ gem.add_runtime_dependency 'resque-retry', '~> 1.5'
26
+ gem.add_runtime_dependency 'god', '~> 0.13.4'
27
+
28
+ gem.add_runtime_dependency 'multi_json'
29
+ gem.add_runtime_dependency 'rake'
30
+
31
+ gem.add_development_dependency 'bundler'
32
+ gem.add_development_dependency 'rspec', '~> 2.14'
33
+ gem.add_development_dependency 'rspec-its'
34
+ gem.add_development_dependency 'simplecov'
35
+ gem.add_development_dependency 'appraisal', '>= 1.0.2'
36
+ gem.add_development_dependency 'combustion', '>= 0.5.5'
37
+ gem.add_development_dependency 'mock_redis'
38
+ gem.add_development_dependency 'timecop'
39
+ gem.add_development_dependency 'pry-byebug'
40
+ end
@@ -0,0 +1,45 @@
1
+ defaults:
2
+ max_age: 10
3
+ max_size: 10
4
+ max_failures_count_per_5m: 5
5
+ max_failures_count_per_1h: 60
6
+ channel: default
7
+
8
+ warn_age: 8
9
+ warn_size: 7
10
+ warn_failures_count_per_5m: 3
11
+ warn_failures_count_per_1h: 30
12
+ warn_channel: default_warnings
13
+
14
+ queues:
15
+ ?
16
+ >
17
+ first,
18
+ third
19
+ :
20
+ max_age: 20
21
+ warn_age: 10
22
+
23
+ max_size: 100
24
+ warn_size: 50
25
+
26
+ max_failures_count_per_5m: 15
27
+ warn_failures_count_per_5m: 7
28
+
29
+ max_failures_count_per_1h: 90
30
+ warn_failures_count_per_1h: 45
31
+
32
+ channel: first
33
+ warn_channel: first_warnings
34
+
35
+ third:
36
+ max_age: 30
37
+ warn_age: 15
38
+ max_failures_count_per_1h: 70
39
+ warn_failures_count_per_1h: 35
40
+
41
+ second_queue:
42
+ channel: [first, second]
43
+ warn_channel: [first_warnings, second_warnings]
44
+
45
+ without_channel: {}
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Resque::JobsController do
4
+ include Rack::Test::Methods
5
+
6
+ class MetaJob
7
+ extend Resque::Plugins::Meta
8
+
9
+ @queue = 'test'
10
+
11
+ def self.perform(_meta_id); end
12
+ end
13
+
14
+ describe '#show' do
15
+ let(:app) { described_class.action(:show) }
16
+
17
+ context 'when id is missing' do
18
+ before do
19
+ get '/'
20
+ end
21
+
22
+ it do
23
+ expect(last_response.status).to eq 404
24
+ expect(last_response.body).to eq '{"message":"not found"}'
25
+ end
26
+ end
27
+
28
+ context 'when id is invalid' do
29
+ before do
30
+ get '/', id: 'xx'
31
+ end
32
+
33
+ it do
34
+ expect(last_response.status).to eq 404
35
+ expect(last_response.body).to eq '{"message":"not found"}'
36
+ end
37
+ end
38
+
39
+ context 'when id is correct' do
40
+ let(:meta) { MetaJob.enqueue }
41
+ let(:meta_id) { meta.meta_id }
42
+ let(:body) do
43
+ {
44
+ enqueued_at: meta.enqueued_at,
45
+ started_at: nil,
46
+ finished_at: nil,
47
+ succeeded: nil,
48
+ failed: nil,
49
+ progress: {num: 0, total: 1, percent: 0.0, message: nil},
50
+ payload: nil
51
+ }.to_json
52
+ end
53
+
54
+ before do
55
+ get '/', id: meta_id
56
+ end
57
+
58
+ it do
59
+ expect(last_response.status).to eq 200
60
+ expect(last_response.body).to eq body
61
+ expect(last_response['Cache-Control']).to eq 'no-cache'
62
+ end
63
+ end
64
+ end
65
+ end