resque-integration 3.4.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 (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