txgh-queue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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