txgh-queue 1.0.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/lib/txgh-queue/application.rb +35 -0
  3. data/lib/txgh-queue/backends/null.rb +19 -0
  4. data/lib/txgh-queue/backends/sqs/config.rb +29 -0
  5. data/lib/txgh-queue/backends/sqs/consumer.rb +30 -0
  6. data/lib/txgh-queue/backends/sqs/history_sequence.rb +62 -0
  7. data/lib/txgh-queue/backends/sqs/job.rb +115 -0
  8. data/lib/txgh-queue/backends/sqs/message_attributes.rb +33 -0
  9. data/lib/txgh-queue/backends/sqs/producer.rb +33 -0
  10. data/lib/txgh-queue/backends/sqs/queue.rb +45 -0
  11. data/lib/txgh-queue/backends/sqs/retry_logic.rb +118 -0
  12. data/lib/txgh-queue/backends/sqs.rb +36 -0
  13. data/lib/txgh-queue/backends.rb +22 -0
  14. data/lib/txgh-queue/config.rb +48 -0
  15. data/lib/txgh-queue/error_handlers/github.rb +47 -0
  16. data/lib/txgh-queue/error_handlers/server_response.rb +22 -0
  17. data/lib/txgh-queue/error_handlers/standard_errors.rb +15 -0
  18. data/lib/txgh-queue/error_handlers/transifex.rb +23 -0
  19. data/lib/txgh-queue/error_handlers/txgh_errors.rb +27 -0
  20. data/lib/txgh-queue/error_handlers.rb +9 -0
  21. data/lib/txgh-queue/job.rb +77 -0
  22. data/lib/txgh-queue/result.rb +26 -0
  23. data/lib/txgh-queue/status.rb +51 -0
  24. data/lib/txgh-queue/supervisor.rb +76 -0
  25. data/lib/txgh-queue/version.rb +3 -0
  26. data/lib/txgh-queue/webhooks/github/request_handler.rb +34 -0
  27. data/lib/txgh-queue/webhooks/github.rb +7 -0
  28. data/lib/txgh-queue/webhooks/transifex/request_handler.rb +21 -0
  29. data/lib/txgh-queue/webhooks/transifex.rb +7 -0
  30. data/lib/txgh-queue/webhooks.rb +6 -0
  31. data/lib/txgh-queue.rb +14 -0
  32. data/spec/application_spec.rb +97 -0
  33. data/spec/backends/sqs/config_spec.rb +30 -0
  34. data/spec/backends/sqs/consumer_spec.rb +34 -0
  35. data/spec/backends/sqs/history_sequence_spec.rb +75 -0
  36. data/spec/backends/sqs/job_spec.rb +189 -0
  37. data/spec/backends/sqs/message_attributes_spec.rb +64 -0
  38. data/spec/backends/sqs/producer_spec.rb +32 -0
  39. data/spec/backends/sqs/queue_spec.rb +65 -0
  40. data/spec/backends/sqs/retry_logic_spec.rb +157 -0
  41. data/spec/backends/sqs_spec.rb +57 -0
  42. data/spec/backends_spec.rb +31 -0
  43. data/spec/config_spec.rb +15 -0
  44. data/spec/error_handlers/github_spec.rb +30 -0
  45. data/spec/error_handlers/server_response_spec.rb +36 -0
  46. data/spec/error_handlers/standard_errors_spec.rb +22 -0
  47. data/spec/error_handlers/transifex_spec.rb +43 -0
  48. data/spec/error_handlers/txgh_errors_spec.rb +25 -0
  49. data/spec/helpers/env_helpers.rb +13 -0
  50. data/spec/helpers/nil_logger.rb +10 -0
  51. data/spec/helpers/sqs/sqs_test_message.rb +47 -0
  52. data/spec/helpers/test_backend.rb +54 -0
  53. data/spec/job_spec.rb +111 -0
  54. data/spec/result_spec.rb +63 -0
  55. data/spec/spec_helper.rb +66 -0
  56. data/spec/status_spec.rb +68 -0
  57. data/spec/supervisor_spec.rb +40 -0
  58. data/spec/webhooks/github/request_handler_spec.rb +145 -0
  59. data/spec/webhooks/transifex/request_handler_spec.rb +87 -0
  60. data/txgh-queue.gemspec +24 -0
  61. metadata +172 -0
@@ -0,0 +1,22 @@
1
+ require 'txgh-server'
2
+
3
+ module TxghQueue
4
+ module ErrorHandlers
5
+ class ServerResponse
6
+ class << self
7
+ def can_handle?(error_or_response)
8
+ error_or_response.is_a?(TxghServer::Response)
9
+ end
10
+
11
+ def status_for(response)
12
+ case response.status.to_i / 100
13
+ when 2
14
+ Status.ok
15
+ else
16
+ Status.fail
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module TxghQueue
2
+ module ErrorHandlers
3
+ class StandardErrors
4
+ class << self
5
+ def can_handle?(error_or_response)
6
+ error_or_response.is_a?(StandardError)
7
+ end
8
+
9
+ def status_for(response)
10
+ Status.fail
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ require 'txgh'
2
+
3
+ module TxghQueue
4
+ module ErrorHandlers
5
+ class Transifex
6
+ ERROR_CLASSES = {
7
+ Txgh::TransifexApiError => Status.retry_with_delay,
8
+ Txgh::TransifexNotFoundError => Status.fail,
9
+ Txgh::TransifexUnauthorizedError => Status.fail
10
+ }
11
+
12
+ class << self
13
+ def can_handle?(error_or_response)
14
+ ERROR_CLASSES.any? { |klass, _| error_or_response.class <= klass }
15
+ end
16
+
17
+ def status_for(error)
18
+ ERROR_CLASSES[error.class]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'txgh'
2
+
3
+ module TxghQueue
4
+ module ErrorHandlers
5
+ class TxghErrors
6
+ ERROR_CLASSES = {
7
+ Txgh::ConfigNotFoundError => Status.fail,
8
+ Txgh::GitConfigNotFoundError => Status.fail,
9
+ Txgh::InvalidProviderError => Status.fail,
10
+ Txgh::ProjectConfigNotFoundError => Status.fail,
11
+ Txgh::RepoConfigNotFoundError => Status.fail,
12
+ Txgh::TxghError => Status.fail,
13
+ Txgh::TxghInternalError => Status.fail
14
+ }
15
+
16
+ class << self
17
+ def can_handle?(error_or_response)
18
+ ERROR_CLASSES.any? { |klass, _| error_or_response.class <= klass }
19
+ end
20
+
21
+ def status_for(error)
22
+ ERROR_CLASSES[error.class]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module TxghQueue
2
+ module ErrorHandlers
3
+ autoload :Github, 'txgh-queue/error_handlers/github'
4
+ autoload :ServerResponse, 'txgh-queue/error_handlers/server_response'
5
+ autoload :StandardErrors, 'txgh-queue/error_handlers/standard_errors'
6
+ autoload :Transifex, 'txgh-queue/error_handlers/transifex'
7
+ autoload :TxghErrors, 'txgh-queue/error_handlers/txgh_errors'
8
+ end
9
+ end
@@ -0,0 +1,77 @@
1
+ require 'txgh'
2
+ require 'txgh-server'
3
+
4
+ module TxghQueue
5
+ class Job
6
+ Github = TxghServer::Webhooks::Github
7
+ Transifex = TxghServer::Webhooks::Transifex
8
+ include TxghServer::ResponseHelpers
9
+
10
+ attr_reader :logger
11
+
12
+ def initialize(logger)
13
+ @logger = logger
14
+ end
15
+
16
+ def process(payload)
17
+ Supervisor.supervise do
18
+ config = config_from(payload)
19
+ project = config.transifex_project
20
+ repo = config.github_repo
21
+
22
+ case payload.fetch('txgh_event')
23
+ when 'github.push'
24
+ handle_github_push(project, repo, payload)
25
+ when 'github.delete'
26
+ handle_github_delete(project, repo, payload)
27
+ when 'transifex.hook'
28
+ handle_transifex_hook(project, repo, payload)
29
+ else
30
+ handle_unexpected
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def config_from(payload)
38
+ Txgh::Config::KeyManager.config_from_repo(payload.fetch('repo_name'))
39
+ end
40
+
41
+ def handle_github_push(project, repo, payload)
42
+ attributes = Github::PushAttributes.new(payload)
43
+ handler = Github::PushHandler.new(project, repo, logger, attributes)
44
+ execute(handler)
45
+ end
46
+
47
+ def handle_github_delete(project, repo, payload)
48
+ attributes = Github::DeleteAttributes.new(payload)
49
+ handler = Github::DeleteHandler.new(project, repo, logger, attributes)
50
+ execute(handler)
51
+ end
52
+
53
+ def handle_transifex_hook(project, repo, payload)
54
+ handler = Transifex::HookHandler.new(
55
+ project: project,
56
+ repo: repo,
57
+ resource_slug: payload['resource'],
58
+ language: payload['language'],
59
+ logger: logger
60
+ )
61
+
62
+ execute(handler)
63
+ end
64
+
65
+ def execute(handler)
66
+ if TxghQueue::Config.processing_enabled?
67
+ handler.execute
68
+ else
69
+ respond_with(200, 'Ok')
70
+ end
71
+ end
72
+
73
+ def handle_unexpected
74
+ respond_with_error(400, 'Unexpected event type')
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ module TxghQueue
2
+ class Result
3
+ attr_reader :status, :response_or_error
4
+
5
+ def initialize(status, response_or_error)
6
+ @status = status
7
+ @response_or_error = response_or_error
8
+ end
9
+
10
+ def has_response?
11
+ response_or_error.is_a?(TxghServer::Response)
12
+ end
13
+
14
+ def response
15
+ return response_or_error if has_response?
16
+ end
17
+
18
+ def has_error?
19
+ response_or_error.is_a?(Exception)
20
+ end
21
+
22
+ def error
23
+ return response_or_error if has_error?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ module TxghQueue
2
+ class Status
3
+ class << self
4
+ def retry_without_delay
5
+ @retry ||= new(status: :retry_without_delay)
6
+ end
7
+
8
+ def retry_with_delay
9
+ @retry_with_delay ||= new(status: :retry_with_delay)
10
+ end
11
+
12
+ def fail
13
+ @fail ||= new(status: :fail)
14
+ end
15
+
16
+ def ok
17
+ @ok ||= new(status: :ok)
18
+ end
19
+ end
20
+
21
+ attr_reader :status
22
+
23
+ def initialize(options = {})
24
+ @status = options.fetch(:status)
25
+ end
26
+
27
+ def retry?
28
+ retry_with_delay? || retry_without_delay?
29
+ end
30
+
31
+ def retry_with_delay?
32
+ status == :retry_with_delay
33
+ end
34
+
35
+ def retry_without_delay?
36
+ status == :retry_without_delay
37
+ end
38
+
39
+ def fail?
40
+ status == :fail
41
+ end
42
+
43
+ def ok?
44
+ status == :ok
45
+ end
46
+
47
+ def to_s
48
+ status.to_s
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,76 @@
1
+ require 'txgh'
2
+
3
+ module TxghQueue
4
+ class UnexpectedResponse < StandardError; end
5
+
6
+ class Supervisor
7
+ # ErrorHandlers::StandardErrors should always come last as a catch-all for
8
+ # unexpected errors. All errors handled by this supervisor inherit from
9
+ # StandardError, so putting it too early in the handler list may cause an
10
+ # error to be mis-handled.
11
+ ERROR_HANDLERS = [
12
+ ErrorHandlers::ServerResponse,
13
+ ErrorHandlers::Github,
14
+ ErrorHandlers::Transifex,
15
+ ErrorHandlers::TxghErrors,
16
+ ErrorHandlers::StandardErrors
17
+ ]
18
+
19
+ class << self
20
+ def supervise(&block)
21
+ new(&block).execute
22
+ end
23
+ end
24
+
25
+ attr_reader :block
26
+
27
+ def initialize(&block)
28
+ @block = block
29
+ end
30
+
31
+ def execute
32
+ response = block.call
33
+ rescue StandardError => e
34
+ status = status_for_error(e)
35
+ Result.new(status, e)
36
+ else
37
+ status = status_for_response(response)
38
+ Result.new(status, response)
39
+ end
40
+
41
+ private
42
+
43
+ def status_for_response(response)
44
+ klass = find_error_handler_class_for(response)
45
+
46
+ unless klass
47
+ message = unexpected_response_error_message(response)
48
+ raise UnexpectedResponse, message
49
+ end
50
+
51
+ klass.status_for(response)
52
+ end
53
+
54
+ def unexpected_response_error_message(response)
55
+ return response unless response.respond_to?(:status)
56
+ return response unless response.respond_to?(:body)
57
+ "#{response.status} #{response.body}"
58
+ end
59
+
60
+ def status_for_error(error)
61
+ # Don't bother handling the case where find_behavior_class returns nil
62
+ # since it will realistically never occur. The execute method above
63
+ # rescues all StandardErrors, which will always be matched by
64
+ # ErrorBehavior::StandardErrors. In cases where errors that don't inherit
65
+ # from StandardError are raised, execute won't catch them and therefore
66
+ # won't call this method.
67
+ find_error_handler_class_for(error).status_for(error)
68
+ end
69
+
70
+ def find_error_handler_class_for(response_or_error)
71
+ ERROR_HANDLERS.find do |klass|
72
+ klass.can_handle?(response_or_error)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module TxghQueue
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,34 @@
1
+ require 'txgh-server'
2
+
3
+ module TxghQueue
4
+ module Webhooks
5
+ module Github
6
+ class RequestHandler < ::TxghServer::Webhooks::Github::RequestHandler
7
+ def handle_request
8
+ handle_safely do
9
+ case github_event
10
+ when 'push', 'delete'
11
+ txgh_event = "github.#{github_event}"
12
+
13
+ result = ::TxghQueue::Config.backend
14
+ .producer_for(txgh_event, logger)
15
+ .enqueue(attributes.to_h.merge(txgh_event: txgh_event))
16
+
17
+ respond_with(202, result.to_json)
18
+ when 'ping'
19
+ ping_handler.execute
20
+ else
21
+ respond_with_error(400, "Event '#{github_event}' cannot be enqueued")
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def ping_handler
29
+ @ping_handler ||= TxghServer::Webhooks::Github::PingHandler.new(logger)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module TxghQueue
2
+ module Webhooks
3
+ module Github
4
+ autoload :RequestHandler, 'txgh-queue/webhooks/github/request_handler'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ require 'txgh-server'
2
+
3
+ module TxghQueue
4
+ module Webhooks
5
+ module Transifex
6
+ class RequestHandler < ::TxghServer::Webhooks::Transifex::RequestHandler
7
+ TXGH_EVENT = 'transifex.hook'
8
+
9
+ def handle_request
10
+ handle_safely do
11
+ result = ::TxghQueue::Config.backend
12
+ .producer_for(TXGH_EVENT, logger)
13
+ .enqueue(payload.merge(txgh_event: TXGH_EVENT))
14
+
15
+ respond_with(202, result.to_json)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module TxghQueue
2
+ module Webhooks
3
+ module Transifex
4
+ autoload :RequestHandler, 'txgh-queue/webhooks/transifex/request_handler'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module TxghQueue
2
+ module Webhooks
3
+ autoload :Github, 'txgh-queue/webhooks/github'
4
+ autoload :Transifex, 'txgh-queue/webhooks/transifex'
5
+ end
6
+ end
data/lib/txgh-queue.rb ADDED
@@ -0,0 +1,14 @@
1
+ module TxghQueue
2
+ autoload :Backends, 'txgh-queue/backends'
3
+ autoload :Config, 'txgh-queue/config'
4
+ autoload :ErrorHandlers, 'txgh-queue/error_handlers'
5
+ autoload :Job, 'txgh-queue/job'
6
+ autoload :Status, 'txgh-queue/status'
7
+ autoload :Supervisor, 'txgh-queue/supervisor'
8
+ autoload :Result, 'txgh-queue/result'
9
+ autoload :Webhooks, 'txgh-queue/webhooks'
10
+ autoload :WebhookEndpoints, 'txgh-queue/application'
11
+
12
+ Backends.register('null', TxghQueue::Backends::Null)
13
+ Backends.register('sqs', TxghQueue::Backends::Sqs)
14
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ require 'helpers/github_payload_builder'
5
+ require 'helpers/standard_txgh_setup'
6
+
7
+ describe TxghQueue::WebhookEndpoints, auto_configure: true do
8
+ include Rack::Test::Methods
9
+ include StandardTxghSetup
10
+ include TxghServer::ResponseHelpers
11
+
12
+ def app
13
+ TxghQueue::WebhookEndpoints
14
+ end
15
+
16
+ let(:config) do
17
+ Txgh::Config::ConfigPair.new(project_config, repo_config)
18
+ end
19
+
20
+ let(:backend) { TxghQueue::Config.backend }
21
+
22
+ describe '/transifex/enqueue' do
23
+ def sign_with(body)
24
+ header(
25
+ TxghServer::TransifexRequestAuth::TRANSIFEX_HEADER,
26
+ TxghServer::TransifexRequestAuth.header_value(
27
+ body, config.transifex_project.webhook_secret
28
+ )
29
+ )
30
+ end
31
+
32
+ let(:producer) { backend.producer_for('transifex.hook') }
33
+ let(:params) do
34
+ {
35
+ 'project' => project_name,
36
+ 'resource' => resource_slug,
37
+ 'language' => language,
38
+ 'translated' => '100'
39
+ }
40
+ end
41
+
42
+ it 'enqueues a new job' do
43
+ payload = URI.encode_www_form(params.to_a)
44
+ sign_with payload
45
+
46
+ expect { post '/transifex/enqueue', payload }.to(
47
+ change { producer.enqueued_jobs.size }.from(0).to(1)
48
+ )
49
+
50
+ expect(last_response).to be_accepted
51
+
52
+ job = producer.enqueued_jobs.first
53
+ expect(job[:payload]).to include(
54
+ project: project_name,
55
+ resource: resource_slug,
56
+ language: language,
57
+ translated: '100',
58
+ txgh_event: 'transifex.hook'
59
+ )
60
+ end
61
+ end
62
+
63
+ describe '/github/enqueue' do
64
+ def sign_with(body)
65
+ header(
66
+ TxghServer::GithubRequestAuth::GITHUB_HEADER,
67
+ TxghServer::GithubRequestAuth.header_value(
68
+ body, config.github_repo.webhook_secret
69
+ )
70
+ )
71
+ end
72
+
73
+ let(:producer) { backend.producer_for('github.push') }
74
+
75
+ it 'enqueues a new job' do
76
+ payload = GithubPayloadBuilder.push_payload(repo_name, ref)
77
+ payload.add_commit
78
+
79
+ sign_with payload.to_json
80
+ header 'X-GitHub-Event', 'push'
81
+
82
+ expect { post '/github/enqueue', payload.to_json }.to(
83
+ change { producer.enqueued_jobs.size }.from(0).to(1)
84
+ )
85
+
86
+ expect(last_response).to be_accepted
87
+
88
+ job = producer.enqueued_jobs.first
89
+ expect(job[:payload]).to include(
90
+ event: 'push',
91
+ txgh_event: 'github.push',
92
+ repo_name: repo_name,
93
+ ref: "refs/#{ref}"
94
+ )
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue::Backends
4
+
5
+ describe Sqs::Config, auto_configure: true do
6
+ let(:queue_config) { sqs_queue_config }
7
+
8
+ describe '.queues' do
9
+ it 'lists all queues' do
10
+ queues = described_class.queues
11
+ queues.each { |q| expect(q).to be_a(Sqs::Queue) }
12
+ expect(queues.map(&:name).sort).to eq(%w(test-queue test-queue-2))
13
+ end
14
+ end
15
+
16
+ describe '.failure_queue' do
17
+ it 'returns the failure queue' do
18
+ expect(described_class.failure_queue).to be_a(Sqs::Queue)
19
+ expect(described_class.failure_queue.name).to eq('test-failure-queue')
20
+ end
21
+ end
22
+
23
+ describe '.get_queue' do
24
+ it 'pulls out a single queue object' do
25
+ queue = described_class.get_queue('test-queue')
26
+ expect(queue).to be_a(Sqs::Queue)
27
+ expect(queue.name).to eq('test-queue')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'helpers/sqs/sqs_test_message'
3
+
4
+ include TxghQueue
5
+ include TxghQueue::Backends
6
+
7
+ describe Sqs::Consumer, auto_configure: true do
8
+ let(:queue_config) { sqs_queue_config }
9
+ let(:queues) { Sqs::Config.queues }
10
+ let(:logger) { NilLogger.new }
11
+ let(:message) { SqsTestMessage.new('abc123', '{}') }
12
+ let(:consumer) { described_class.new(queues, logger) }
13
+
14
+ it 'executes one job in each queue' do
15
+ queues.each do |queue|
16
+ job = double(:Job)
17
+ expect(queue).to receive(:receive_message).and_return(message.to_bundle)
18
+ expect(job).to receive(:complete)
19
+ expect(Sqs::Job).to(
20
+ receive(:new).with(message, queue, logger).and_return(job)
21
+ )
22
+ end
23
+
24
+ consumer.work
25
+ end
26
+
27
+ it 'reports errors' do
28
+ errors = []
29
+ Txgh.events.subscribe('errors') { |e| errors << e }
30
+ expect(queues.first).to receive(:receive_message).and_raise(StandardError, 'jelly beans')
31
+ expect { consumer.work }.to change { errors.size }.from(0).to(1)
32
+ expect(errors.first.message).to eq('jelly beans')
33
+ end
34
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ require 'helpers/sqs/sqs_test_message'
3
+
4
+ include TxghQueue
5
+ include TxghQueue::Backends
6
+
7
+ describe Sqs::HistorySequence do
8
+ let(:message) { SqsTestMessage.new('abc123', '{}', attributes_hash) }
9
+ let(:attributes_hash) do
10
+ {
11
+ 'history_sequence' => {
12
+ 'string_value' => [{
13
+ 'status' => 'retry_without_delay'
14
+ }].to_json
15
+ }
16
+ }
17
+ end
18
+
19
+ describe '.from_message' do
20
+ it 'extracts the correct attributes from the given hash' do
21
+ sequence = described_class.from_message(message)
22
+ expect(sequence.sequence).to eq([
23
+ { status: 'retry_without_delay' }
24
+ ])
25
+ end
26
+ end
27
+
28
+ describe '.from_h' do
29
+ it 'extracts the correct attributes from the given hash' do
30
+ sequence = described_class.from_h(attributes_hash)
31
+ expect(sequence.sequence).to eq([
32
+ { status: 'retry_without_delay' }
33
+ ])
34
+ end
35
+ end
36
+
37
+ context 'with a sequence' do
38
+ let(:sequence) { described_class.from_message(message) }
39
+
40
+ describe '#add' do
41
+ it 'adds the given object to the sequence' do
42
+ expect { sequence.add('abc') }.to(
43
+ change { sequence.sequence.size }.by(1)
44
+ )
45
+
46
+ expect(sequence.sequence.last).to eq('abc')
47
+ end
48
+ end
49
+
50
+ describe '#to_h' do
51
+ it 'serializes the sequence into a hash' do
52
+ expect(sequence.to_h).to eq(
53
+ string_value: [{'status' => 'retry_without_delay'}].to_json,
54
+ data_type: 'String'
55
+ )
56
+ end
57
+ end
58
+
59
+ describe '#dup' do
60
+ it 'deep copies the sequence' do
61
+ copied_sequence = sequence.dup
62
+ expect(sequence.object_id).to_not eq(copied_sequence.object_id)
63
+ expect(sequence.sequence.object_id).to_not(
64
+ eq(copied_sequence.sequence.object_id)
65
+ )
66
+ end
67
+ end
68
+
69
+ describe '#current' do
70
+ it 'returns the last element in the sequence' do
71
+ expect(sequence.current).to eq(status: 'retry_without_delay')
72
+ end
73
+ end
74
+ end
75
+ end