ctt-background-jobs 0.8.0
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.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CTT-Background-Jobs.gemspec +37 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -0
- data/Rakefile +29 -0
- data/lib/background_jobs/configuration.rb +27 -0
- data/lib/background_jobs/job.rb +17 -0
- data/lib/background_jobs/job_factory.rb +9 -0
- data/lib/background_jobs/job_id_generator.rb +15 -0
- data/lib/background_jobs/job_notifier.rb +31 -0
- data/lib/background_jobs/job_queue_factory.rb +36 -0
- data/lib/background_jobs/job_registry.rb +65 -0
- data/lib/background_jobs/queue_service.rb +17 -0
- data/lib/background_jobs/strategies/direct_call/job_queue.rb +11 -0
- data/lib/background_jobs/strategies/sidekiq/job_adapter.rb +21 -0
- data/lib/background_jobs/strategies/sidekiq/job_attributes_adapter.rb +141 -0
- data/lib/background_jobs/strategies/sidekiq/job_queue.rb +42 -0
- data/lib/background_jobs/strategies/sidekiq/setup.rb +31 -0
- data/lib/version.rb +3 -0
- data/spec/fast_helper.rb +11 -0
- data/spec/integration/sidekiq_spec.rb +34 -0
- data/spec/lib/background_jobs/configuration_spec.rb +13 -0
- data/spec/lib/background_jobs/job_factory_fast_spec.rb +24 -0
- data/spec/lib/background_jobs/job_fast_spec.rb +38 -0
- data/spec/lib/background_jobs/job_id_generator_fast_spec.rb +23 -0
- data/spec/lib/background_jobs/job_notififer_spec.rb +9 -0
- data/spec/lib/background_jobs/job_queue_factory_fast_spec.rb +58 -0
- data/spec/lib/background_jobs/queue_service_fast_spec.rb +36 -0
- data/spec/lib/background_jobs/strategies/direct_call/job_queue_spec.rb +27 -0
- data/spec/lib/background_jobs/strategies/sidekiq/job_adapter_spec.rb +30 -0
- data/spec/lib/background_jobs/strategies/sidekiq/job_attributes_adapter_spec.rb +129 -0
- data/spec/lib/background_jobs/strategies/sidekiq/job_queue_spec.rb +63 -0
- data/spec/lib/background_jobs/torquebox_jobs_queue_fast_spec.rb +31 -0
- data/spec/support/job_queue.rb +7 -0
- metadata +281 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'background_jobs/job_registry'
|
3
|
+
require 'background_jobs/strategies/sidekiq/job_adapter'
|
4
|
+
require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
|
5
|
+
|
6
|
+
module BackgroundJobs
|
7
|
+
module Sidekiq
|
8
|
+
class JobQueue
|
9
|
+
|
10
|
+
def enqueue(job_type, job_id, attributes, options = {})
|
11
|
+
priority = options[:priority] || 1
|
12
|
+
unique = options[:unique] || false
|
13
|
+
at = (Time.now + options[:in]).to_i if options[:in]
|
14
|
+
|
15
|
+
return if unique && is_scheduled?(job_type)
|
16
|
+
|
17
|
+
queue = BackgroundJobs.job_registry.get_type(job_type)
|
18
|
+
attributes.unshift job_id
|
19
|
+
attributes.unshift job_type
|
20
|
+
|
21
|
+
args = {
|
22
|
+
'queue' => queue.to_s, # queue by priority?
|
23
|
+
'class' => JobAdapter,
|
24
|
+
'args' => JobAttributesAdapter.new(attributes).encode
|
25
|
+
}
|
26
|
+
|
27
|
+
args['at'] = at if at
|
28
|
+
|
29
|
+
::Sidekiq::Client.push(args)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def is_scheduled?(job_name)
|
35
|
+
entries = ::Sidekiq::ScheduledSet.new.entries
|
36
|
+
job_names = entries.map(&:item).map {|item| item['args'].first }
|
37
|
+
|
38
|
+
job_names.include?(job_name.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'background_jobs/job_registry'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
module Sidekiq
|
6
|
+
def self.setup(options)
|
7
|
+
::Sidekiq.configure_server do |config|
|
8
|
+
config.redis = options[:redis] if options[:redis]
|
9
|
+
end
|
10
|
+
|
11
|
+
BackgroundJobs.job_registry.register_callback = method(:register_job_callback)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.register_job_callback(job_metadata)
|
15
|
+
options = job_metadata.options
|
16
|
+
|
17
|
+
BackgroundJobs::QueueService.enqueue(
|
18
|
+
job_metadata.name, [], :in => options[:schedule], :unique => true
|
19
|
+
) if options[:schedule]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.is_scheduled?(job_name)
|
25
|
+
entries = ::Sidekiq::ScheduledSet.new.entries
|
26
|
+
job_names = entries.map(&:item).map {|item| item['args'].first }
|
27
|
+
|
28
|
+
job_names.include?(job_name.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/version.rb
ADDED
data/spec/fast_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
|
+
ENV["RAILS_ENV"] ||= 'test'
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'shoulda'
|
6
|
+
require 'require_relative'
|
7
|
+
require 'timecop'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.mock_with :mocha
|
11
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/configuration'
|
3
|
+
require 'background_jobs/queue_service'
|
4
|
+
require 'background_jobs/strategies/sidekiq/setup'
|
5
|
+
|
6
|
+
describe 'Sidekiq' do
|
7
|
+
|
8
|
+
let(:sidekiq_fake_job) { Class.new }
|
9
|
+
|
10
|
+
let(:clock) { stub('clock') }
|
11
|
+
let(:clock_schedule) { stub('clock_schedule') }
|
12
|
+
|
13
|
+
before do
|
14
|
+
BackgroundJobs.configure do |config|
|
15
|
+
config.strategy = 'sidekiq'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with schedule' do
|
20
|
+
let(:scheduled_set) { stub('scheduled_set') }
|
21
|
+
|
22
|
+
it 'should schedule the job if schedule is set' do
|
23
|
+
BackgroundJobs::QueueService.expects(:enqueue).with(:fake_job, [], :in => 60*10, :unique => true)
|
24
|
+
|
25
|
+
BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job, :schedule => 60*10
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not schedule the job if schedule not set' do
|
30
|
+
BackgroundJobs::QueueService.expects(:enqueue).never
|
31
|
+
|
32
|
+
BackgroundJobs.register_job :fake_job, :core, sidekiq_fake_job
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/configuration'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
describe Configuration do
|
6
|
+
describe '#strategy' do
|
7
|
+
context 'with env configuration set' do
|
8
|
+
before { BackgroundJobs.configure {|c| c.strategy = 'foo' } }
|
9
|
+
it { BackgroundJobs.config.strategy.should == 'foo' }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/job_factory'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
describe JobFactory do
|
6
|
+
before do
|
7
|
+
BackgroundJobs.register_job :foo, :bar, Imitate.mock('Foo')
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.build' do
|
11
|
+
context 'for existing job' do
|
12
|
+
it 'should create job instance' do
|
13
|
+
JobFactory.build(:foo).should be_instance_of Foo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'for not-existing job' do
|
18
|
+
it 'should throw an exception' do
|
19
|
+
expect { JobFactory.build(:not_existing_job) }.to raise_error("Unknown job 'not_existing_job'!")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/job'
|
3
|
+
|
4
|
+
describe BackgroundJobs::Job do
|
5
|
+
before do
|
6
|
+
Imitate.mock 'Airbrake' do
|
7
|
+
def self.notify(param); end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:job) { BackgroundJobs::Job.new }
|
12
|
+
|
13
|
+
subject { job }
|
14
|
+
|
15
|
+
describe '#perform' do
|
16
|
+
it { expect { subject.perform }.to raise_error NotImplementedError }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#id=' do
|
20
|
+
before { job.id = 123 }
|
21
|
+
|
22
|
+
it { subject.instance_variable_get(:@id).should eq 123 }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#execute' do
|
26
|
+
it { expect { subject.execute }.to raise_error NotImplementedError }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'error' do
|
30
|
+
let(:exception) { stub('exception') }
|
31
|
+
|
32
|
+
before do
|
33
|
+
Airbrake.expects(:notify).with(exception)
|
34
|
+
end
|
35
|
+
|
36
|
+
it { job.error('whatever', exception) }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/job_id_generator'
|
3
|
+
require 'imitate'
|
4
|
+
|
5
|
+
describe BackgroundJobs::JobIdGenerator do
|
6
|
+
let(:generator) do
|
7
|
+
Imitate.mock('Generator') do
|
8
|
+
include BackgroundJobs::JobIdGenerator
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.generate' do
|
13
|
+
let(:result1) { generator.generate_job_id }
|
14
|
+
let(:result2) { generator.generate_job_id }
|
15
|
+
let(:result3) { generator.generate_job_id }
|
16
|
+
|
17
|
+
subject { result1 }
|
18
|
+
|
19
|
+
it { should_not == result2 }
|
20
|
+
it { should_not == result3 }
|
21
|
+
it(:result2) { should_not == result3 }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/job_queue_factory'
|
3
|
+
require 'background_jobs/strategies/sidekiq/job_queue'
|
4
|
+
require 'background_jobs/strategies/direct_call/job_queue'
|
5
|
+
|
6
|
+
module BackgroundJobs
|
7
|
+
describe JobQueueFactory do
|
8
|
+
describe '.build' do
|
9
|
+
subject { JobQueueFactory.build }
|
10
|
+
|
11
|
+
context 'sidekiq' do
|
12
|
+
before { configure_strategy('sidekiq', options) }
|
13
|
+
|
14
|
+
let(:sidekiq_job_queue) { stub('sidekiq_job_queue') }
|
15
|
+
|
16
|
+
let(:options) { stub('options') }
|
17
|
+
|
18
|
+
it {
|
19
|
+
Sidekiq::JobQueue.expects(:new).returns(sidekiq_job_queue)
|
20
|
+
JobQueueFactory.build.should == sidekiq_job_queue
|
21
|
+
}
|
22
|
+
|
23
|
+
after { clear_job_queue_strategy }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'direct_call' do
|
27
|
+
before { configure_strategy('direct_call') }
|
28
|
+
|
29
|
+
it { should be_instance_of DirectCall::JobQueue }
|
30
|
+
|
31
|
+
after { clear_job_queue_strategy }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with no strategy configured' do
|
35
|
+
before { clear_job_queue_strategy }
|
36
|
+
|
37
|
+
it "will blow up" do
|
38
|
+
expect {
|
39
|
+
JobQueueFactory.build
|
40
|
+
}.to raise_exception UnknownBackgroundJobsQueueStrategy
|
41
|
+
end
|
42
|
+
|
43
|
+
after { configure_strategy('delayed_job') }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear_job_queue_strategy
|
50
|
+
configure_strategy nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def configure_strategy(name, options = {})
|
54
|
+
BackgroundJobs.configure do |config|
|
55
|
+
config.strategy = name
|
56
|
+
config.strategy_options = options
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/queue_service'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
describe QueueService do
|
6
|
+
let(:job_type) { :fix_all_bugs_while_we_sleep }
|
7
|
+
|
8
|
+
let(:priority) { 1 }
|
9
|
+
|
10
|
+
let(:data) { [1, 2, 3, 'foo', {1 => :uno}] }
|
11
|
+
|
12
|
+
let(:job_id) { 'job_id' }
|
13
|
+
|
14
|
+
let(:expected_queue_params) { data }
|
15
|
+
|
16
|
+
let(:strategy) { stub('strategy') }
|
17
|
+
|
18
|
+
describe '.enqueue' do
|
19
|
+
let(:delay) { stub('delay') }
|
20
|
+
|
21
|
+
before do
|
22
|
+
QueueService.stubs(:generate_job_id).returns(job_id)
|
23
|
+
|
24
|
+
JobQueueFactory.expects(:build).returns(strategy)
|
25
|
+
|
26
|
+
strategy.expects(:enqueue).with(
|
27
|
+
job_type, job_id, expected_queue_params, :priority => priority, :in => delay
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
subject { QueueService.enqueue(job_type, data, :priority => priority, :in => delay) }
|
32
|
+
|
33
|
+
it { should == job_id }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/strategies/direct_call/job_queue'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
module DirectCall
|
6
|
+
describe JobQueue.new do
|
7
|
+
let(:type) { :foo }
|
8
|
+
|
9
|
+
let(:priority) { 1 }
|
10
|
+
|
11
|
+
let(:attrs) { [10..10, :foo => :bar] }
|
12
|
+
|
13
|
+
let(:job) { stub('job') }
|
14
|
+
|
15
|
+
let(:job_id) { 123 }
|
16
|
+
|
17
|
+
describe '.enqueue' do
|
18
|
+
it 'should create a job and execute it' do
|
19
|
+
JobFactory.expects(:build).with(type, attrs).returns(job)
|
20
|
+
job.expects(:execute)
|
21
|
+
|
22
|
+
JobQueue.new.enqueue(type, job_id, attrs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/strategies/sidekiq/job_queue'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
module Sidekiq
|
6
|
+
describe JobAdapter.new do
|
7
|
+
let(:job) { stub('job', :execute => true) }
|
8
|
+
|
9
|
+
describe '#peform' do
|
10
|
+
subject { JobAdapter.new.perform('foo', '1', :bar, :baz, 'range:[10,1000]') }
|
11
|
+
|
12
|
+
before { JobFactory.stubs(:build).returns(job) }
|
13
|
+
|
14
|
+
it 'should call job factory with appropriate job type and attributes' do
|
15
|
+
JobFactory.expects(:build).with(:foo, [:bar, :baz, 10..1000]).returns(job)
|
16
|
+
job.expects(:id=).with(1)
|
17
|
+
|
18
|
+
subject
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should call job perform with no attributes' do
|
22
|
+
job.expects(:execute)
|
23
|
+
job.expects(:id=).with(1)
|
24
|
+
|
25
|
+
subject
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fast_helper'
|
2
|
+
require 'background_jobs/strategies/sidekiq/job_attributes_adapter'
|
3
|
+
|
4
|
+
module BackgroundJobs
|
5
|
+
module Sidekiq
|
6
|
+
describe JobAttributesAdapter do
|
7
|
+
context 'date' do
|
8
|
+
let(:attributes) { [Date.parse('1st Jan 2011')] }
|
9
|
+
let(:attributes_encoded) { ['date:2011-01-01'] }
|
10
|
+
|
11
|
+
it 'does decode to string with "date:"" prefix' do
|
12
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'does encode to string with "date:"" prefix' do
|
16
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'date range' do
|
21
|
+
let(:attributes) { [Date.parse('1st Jan 2011')..Date.parse('1st Feb 2011')] }
|
22
|
+
let(:attributes_encoded) { ['range:[date:2011-01-01,date:2011-02-01]'] }
|
23
|
+
|
24
|
+
it 'does decode to string with "range:" prefix and encode its boundary dates' do
|
25
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does encode to string with "range:" prefix and encode its boundary dates' do
|
29
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'integers range' do
|
34
|
+
let(:attributes) { [1..10] }
|
35
|
+
let(:attributes_encoded) { ['range:[1,10]'] }
|
36
|
+
|
37
|
+
it 'does decode to string with prefix range:' do
|
38
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'does encode to string with prefix range:' do
|
42
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'integer' do
|
47
|
+
let(:attributes) { [12345] }
|
48
|
+
let(:attributes_encoded) { [12345] }
|
49
|
+
|
50
|
+
it 'is left decodehout any tranformations' do
|
51
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does encode to string with prefix range:' do
|
55
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'hash' do
|
60
|
+
let(:attributes) { [{:foo => 'bar'}] }
|
61
|
+
let(:attributes_encoded) { [{'foo' => 'bar'}] }
|
62
|
+
|
63
|
+
it 'is left decodehout any tranformations' do
|
64
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does encode to string with prefix range:' do
|
68
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'integer, hash and range' do
|
73
|
+
let(:attributes) { [12345, {:foo => 'bar'}, 10..1000] }
|
74
|
+
let(:attributes_encoded) { [12345, {'foo'=>"bar"}, "range:[10,1000]"] }
|
75
|
+
|
76
|
+
it 'does decode all together appropriately to their type' do
|
77
|
+
JobAttributesAdapter.new(attributes_encoded).decode.should eq attributes
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does encode to string with prefix range:' do
|
81
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'unknown group id bug' do
|
86
|
+
let(:attributes) {
|
87
|
+
[
|
88
|
+
"624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
|
89
|
+
{
|
90
|
+
:group_id => 627,
|
91
|
+
:date_range => [Date.parse("2016-12-31")],
|
92
|
+
:week_schedule => {
|
93
|
+
:monday => 0,
|
94
|
+
:tuesday => 1,
|
95
|
+
:wednesday => 2,
|
96
|
+
:thursday => 3,
|
97
|
+
:friday => 2,
|
98
|
+
:saturday => 0,
|
99
|
+
:sunday => 0
|
100
|
+
}
|
101
|
+
}
|
102
|
+
]
|
103
|
+
}
|
104
|
+
|
105
|
+
let(:attributes_encoded) {
|
106
|
+
[
|
107
|
+
"624ea403ad028e03045ba34c923bce0b2a812099bcd815bb1ddf2d92d8aaa560",
|
108
|
+
{
|
109
|
+
"group_id"=>627,
|
110
|
+
"date_range"=>["date:2016-12-31"],
|
111
|
+
"week_schedule"=>{"monday"=>0, "tuesday"=>1, "wednesday"=>2, "thursday"=>3, "friday"=>2, "saturday"=>0, "sunday"=>0}
|
112
|
+
}
|
113
|
+
]
|
114
|
+
}
|
115
|
+
|
116
|
+
it 'does encode well' do
|
117
|
+
JobAttributesAdapter.new(attributes).encode.should eq attributes_encoded
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'does decode well' do
|
121
|
+
decoded = JobAttributesAdapter.new(attributes_encoded).decode
|
122
|
+
|
123
|
+
decoded.should eq attributes
|
124
|
+
decoded.last[:group_id].should == 627
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|