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 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe ErrorHandlers::StandardErrors do
6
+ describe '.can_handle?' do
7
+ it 'can reply to StandardError' do
8
+ expect(described_class.can_handle?(StandardError.new)).to eq(true)
9
+ end
10
+
11
+ it 'can reply to things that inherit from StandardError' do
12
+ expect(described_class.can_handle?(RuntimeError.new)).to eq(true)
13
+ end
14
+ end
15
+
16
+ describe '.status_for' do
17
+ it 'always responds with fail' do
18
+ expect(described_class.status_for(StandardError.new)).to eq(Status.fail)
19
+ expect(described_class.status_for('foo')).to eq(Status.fail)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe ErrorHandlers::Transifex do
6
+ describe '.can_handle?' do
7
+ it 'can reply to transifex api errors' do
8
+ error = Txgh::TransifexApiError.new(500, 'Internal error')
9
+ expect(described_class.can_handle?(error)).to eq(true)
10
+ end
11
+
12
+ it 'can reply to transifex not found errors' do
13
+ error = Txgh::TransifexNotFoundError.new
14
+ expect(described_class.can_handle?(error)).to eq(true)
15
+ end
16
+
17
+ it 'can reply to transifex unauthorized errors' do
18
+ error = Txgh::TransifexUnauthorizedError.new
19
+ expect(described_class.can_handle?(error)).to eq(true)
20
+ end
21
+
22
+ it "can't reply to unsupported error classes" do
23
+ expect(described_class.can_handle?(StandardError.new)).to eq(false)
24
+ end
25
+ end
26
+
27
+ describe '.status_for' do
28
+ it 'retries with delay on api error' do
29
+ error = Txgh::TransifexApiError.new(500, 'Internal error')
30
+ expect(described_class.status_for(error)).to eq(Status.retry_with_delay)
31
+ end
32
+
33
+ it 'fails on not found error' do
34
+ error = Txgh::TransifexNotFoundError.new
35
+ expect(described_class.status_for(error)).to eq(Status.fail)
36
+ end
37
+
38
+ it 'fails on unauthorized error' do
39
+ error = Txgh::TransifexUnauthorizedError.new
40
+ expect(described_class.status_for(error)).to eq(Status.fail)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe ErrorHandlers::TxghErrors do
6
+ describe '.can_handle?' do
7
+ it 'can reply to all configured error classes' do
8
+ described_class::ERROR_CLASSES.keys.each do |klass|
9
+ expect(described_class.can_handle?(klass.new)).to eq(true)
10
+ end
11
+ end
12
+
13
+ it "can't reply to unsupported error classes" do
14
+ expect(described_class.can_handle?(StandardError.new)).to eq(false)
15
+ end
16
+ end
17
+
18
+ describe '.status_for' do
19
+ it 'replies to all configured errors correctly' do
20
+ described_class::ERROR_CLASSES.each_pair do |klass, expected_response|
21
+ expect(described_class.status_for(klass.new)).to eq(expected_response)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module EnvHelpers
2
+ def with_env(env)
3
+ # ENV can't be duped, so use select instead to make a copy
4
+ old_env = ENV.select { true }
5
+ env.each_pair { |k, v| ENV[k] = v }
6
+ yield
7
+ ensure
8
+ # reset back to old vars
9
+ env.each_pair { |k, _| ENV[k] = old_env[k] }
10
+ end
11
+ end
12
+
13
+ EnvHelpers.extend(EnvHelpers)
@@ -0,0 +1,10 @@
1
+ class NilLogger
2
+ def info(*args)
3
+ end
4
+
5
+ def warn(*args)
6
+ end
7
+
8
+ def error(*args)
9
+ end
10
+ end
@@ -0,0 +1,47 @@
1
+ require 'securerandom'
2
+
3
+ class SqsTestMessage
4
+ attr_reader :message_id, :body, :message_attributes
5
+ attr_reader :receipt_handle
6
+
7
+ def initialize(message_id, body, message_attributes = {})
8
+ @message_id = message_id
9
+ @body = body
10
+ @message_attributes = SqsTestMessageAttributes.new(message_attributes)
11
+ @receipt_handle = SecureRandom.hex
12
+ end
13
+
14
+ def to_bundle
15
+ SqsTestMessageBundle.new([self])
16
+ end
17
+ end
18
+
19
+ class SqsTestMessageBundle
20
+ attr_reader :messages
21
+
22
+ def initialize(messages)
23
+ @messages = messages
24
+ end
25
+ end
26
+
27
+ class SqsTestMessageAttributes
28
+ attr_reader :attributes
29
+
30
+ def initialize(attributes)
31
+ @attributes = attributes
32
+ end
33
+
34
+ def [](key)
35
+ if attribute = attributes[key]
36
+ SqsTestMessageAttribute.new(attribute)
37
+ end
38
+ end
39
+ end
40
+
41
+ class SqsTestMessageAttribute
42
+ attr_reader :string_value
43
+
44
+ def initialize(attribute)
45
+ @string_value = attribute['string_value']
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ module TxghQueue
2
+ class TestBackend
3
+ class << self
4
+ def producer_for(event, logger = Txgh::TxLogger.logger)
5
+ producers[event] ||= TestProducer.new(event, logger)
6
+ end
7
+
8
+ def consumer_for(event, logger = Txgh::TxLogger.logger)
9
+ consumers[event] ||= TestConsumer.new(event, logger)
10
+ end
11
+
12
+ def reset!
13
+ @producers = nil
14
+ @consumers = nil
15
+ end
16
+
17
+ private
18
+
19
+ def producers
20
+ @producers ||= {}
21
+ end
22
+
23
+ def consumers
24
+ @consumers ||= {}
25
+ end
26
+ end
27
+ end
28
+
29
+ class TestProducer
30
+ attr_reader :queue_names, :logger, :enqueued_jobs
31
+
32
+ def initialize(event, logger)
33
+ @event = event
34
+ @logger = logger
35
+ @enqueued_jobs = []
36
+ end
37
+
38
+ def enqueue(payload, options = {})
39
+ enqueued_jobs << { payload: payload, options: options }
40
+ end
41
+ end
42
+
43
+ class TestConsumer
44
+ attr_reader :queue_names, :logger
45
+
46
+ def initialize(event, logger)
47
+ @event = event
48
+ @logger = logger
49
+ end
50
+
51
+ def work
52
+ end
53
+ end
54
+ end
data/spec/job_spec.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe Job, auto_configure: true do
6
+ let(:logger) { NilLogger.new }
7
+ let(:repo_name) { 'my_repo' }
8
+ let(:txgh_config) { Txgh::Config::ConfigPair.new(:github_repo, :transifex_project) }
9
+ let(:job) { described_class.new(logger) }
10
+
11
+ before(:each) do
12
+ allow(Txgh::Config::KeyManager).to(
13
+ receive(:config_from_repo).with(repo_name).and_return(txgh_config)
14
+ )
15
+ end
16
+
17
+ shared_examples 'a payload processor' do
18
+ it 'processes the payload' do
19
+ server_response = TxghServer::Response.new(200, 'Ok')
20
+ expect(handler).to receive(:execute).and_return(server_response)
21
+ result = job.process(payload)
22
+ expect(result.status).to eq(Status.ok)
23
+ expect(result.response).to eq(server_response)
24
+ end
25
+
26
+ it 'responds appropriately when an error is raised' do
27
+ expect(handler).to receive(:execute).and_raise(StandardError)
28
+ result = job.process(payload)
29
+ expect(result.status).to eq(Status.fail)
30
+ expect(result.error).to be_a(StandardError)
31
+ end
32
+
33
+ it 'responds appropriately when an error response is returned' do
34
+ server_response = TxghServer::Response.new(404, 'Not found')
35
+ expect(handler).to receive(:execute).and_return(server_response)
36
+ result = job.process(payload)
37
+ expect(result.status).to eq(Status.fail)
38
+ expect(result.response).to eq(server_response)
39
+ end
40
+ end
41
+
42
+ context 'with a push payload' do
43
+ let(:payload) do
44
+ {
45
+ 'repo_name' => repo_name,
46
+ 'event' => 'push',
47
+ 'txgh_event' => 'github.push',
48
+ 'ref' => 'heads/master',
49
+ 'before' => 'abc123',
50
+ 'after' => 'def456',
51
+ 'added_files' => [],
52
+ 'modified_files' => [],
53
+ 'author' => 'Bugs Bunny'
54
+ }
55
+ end
56
+
57
+ describe '#process' do
58
+ let(:handler) { double(:handler) }
59
+
60
+ before(:each) do
61
+ expect(TxghServer::Webhooks::Github::PushHandler).to(
62
+ receive(:new).and_return(handler)
63
+ )
64
+ end
65
+
66
+ it_behaves_like 'a payload processor'
67
+ end
68
+ end
69
+
70
+ context 'with a delete payload' do
71
+ let(:payload) do
72
+ {
73
+ 'repo_name' => repo_name,
74
+ 'event' => 'delete',
75
+ 'txgh_event' => 'github.delete',
76
+ 'ref' => 'heads/master',
77
+ 'ref_type' => 'branch'
78
+ }
79
+ end
80
+
81
+ describe '#process' do
82
+ let(:handler) { double(:handler) }
83
+
84
+ before(:each) do
85
+ expect(TxghServer::Webhooks::Github::DeleteHandler).to(
86
+ receive(:new).and_return(handler)
87
+ )
88
+ end
89
+
90
+ it_behaves_like 'a payload processor'
91
+ end
92
+ end
93
+
94
+ context 'with a payload with an unrecognized event type' do
95
+ let(:payload) do
96
+ {
97
+ 'repo_name' => repo_name,
98
+ 'txgh_event' => 'foobarbazboo'
99
+ }
100
+ end
101
+
102
+ describe '#process' do
103
+ it 'responds with fail' do
104
+ result = job.process(payload)
105
+ expect(result.status).to eq(Status.fail)
106
+ expect(result.response.status).to eq(400)
107
+ expect(result.response.body).to eq([error: 'Unexpected event type'])
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe Result do
6
+ context 'with a response' do
7
+ let(:response) { TxghServer::Response.new(200, 'Ok') }
8
+ let(:result) { Result.new(Status.ok, response) }
9
+
10
+ describe 'has_response?' do
11
+ it 'does have a response' do
12
+ expect(result.has_response?).to eq(true)
13
+ end
14
+ end
15
+
16
+ describe 'has_error?' do
17
+ it 'does not have an error' do
18
+ expect(result.has_error?).to eq(false)
19
+ end
20
+ end
21
+
22
+ describe '#response' do
23
+ it 'returns the response' do
24
+ expect(result.response).to eq(response)
25
+ end
26
+ end
27
+
28
+ describe '#error' do
29
+ it 'returns nil' do
30
+ expect(result.error).to eq(nil)
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'with an error' do
36
+ let(:error) { StandardError.new }
37
+ let(:result) { Result.new(Status.fail, error) }
38
+
39
+ describe 'has_response?' do
40
+ it 'does not have a response' do
41
+ expect(result.has_response?).to eq(false)
42
+ end
43
+ end
44
+
45
+ describe 'has_error?' do
46
+ it 'does have an error' do
47
+ expect(result.has_error?).to eq(true)
48
+ end
49
+ end
50
+
51
+ describe '#response' do
52
+ it 'returns nil' do
53
+ expect(result.response).to eq(nil)
54
+ end
55
+ end
56
+
57
+ describe '#error' do
58
+ it 'returns nil' do
59
+ expect(result.error).to eq(error)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ $:.push(File.join(Gem.loaded_specs['txgh'].full_gem_path, 'spec'))
2
+ $:.push(File.join(Gem.loaded_specs['txgh-server'].full_gem_path, 'spec'))
3
+
4
+ require 'pry-byebug'
5
+ require 'rake'
6
+ require 'rspec'
7
+ require 'txgh'
8
+ require 'txgh-queue'
9
+ require 'txgh-server'
10
+
11
+ require 'helpers/env_helpers'
12
+ require 'helpers/nil_logger'
13
+ require 'helpers/test_backend'
14
+
15
+ RSpec.configure do |config|
16
+ module GlobalLets
17
+ extend RSpec::SharedContext
18
+
19
+ # default config, override in specs if you wanna customize
20
+ let(:queue_config) do
21
+ {
22
+ backend: 'test',
23
+ options: {
24
+ queues: %w(test-queue)
25
+ }
26
+ }
27
+ end
28
+
29
+ # default sqs config; override queue_config with this when working with the
30
+ # sqs backend, i.e. let(:queue_config) { sqs_queue_config }
31
+ let(:sqs_queue_config) do
32
+ {
33
+ backend: 'sqs',
34
+ options: {
35
+ queues: [
36
+ { name: 'test-queue', region: 'us-east-1', events: %w(a b c) },
37
+ { name: 'test-queue-2', region: 'us-west-1', events: %w(c d e) }
38
+ ],
39
+
40
+ failure_queue: {
41
+ name: 'test-failure-queue', region: 'us-east-1'
42
+ }
43
+ }
44
+ }
45
+ end
46
+ end
47
+
48
+ config.include(GlobalLets)
49
+ config.include(EnvHelpers)
50
+
51
+ config.around(:each) do |example|
52
+ if example.metadata[:auto_configure]
53
+ env_vars = { 'TXGH_QUEUE_CONFIG' => "raw://#{YAML.dump(queue_config)}" }
54
+ with_env(env_vars) { example.run }
55
+
56
+ # reset global config
57
+ TxghQueue::Config.reset!
58
+ TxghQueue::TestBackend.reset!
59
+ TxghQueue::Backends::Sqs::Config.reset!
60
+ else
61
+ example.run
62
+ end
63
+ end
64
+ end
65
+
66
+ TxghQueue::Backends.register('test', TxghQueue::TestBackend)
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe Status do
6
+ describe '.retry_without_delay' do
7
+ it 'returns the same object' do
8
+ expect(described_class.retry_without_delay.object_id).to eq(
9
+ described_class.retry_without_delay.object_id
10
+ )
11
+ end
12
+ end
13
+
14
+ describe '.retry_with_delay' do
15
+ it 'returns the same object' do
16
+ expect(described_class.retry_with_delay.object_id).to eq(
17
+ described_class.retry_with_delay.object_id
18
+ )
19
+ end
20
+ end
21
+
22
+ describe '.fail' do
23
+ it 'returns the same object' do
24
+ expect(described_class.fail.object_id).to eq(described_class.fail.object_id)
25
+ end
26
+ end
27
+
28
+ describe '.ok' do
29
+ it 'returns the same object' do
30
+ expect(described_class.ok.object_id).to eq(described_class.ok.object_id)
31
+ end
32
+ end
33
+
34
+ describe '#retry_without_delay?' do
35
+ it 'returns true if the response matches, false otherwise' do
36
+ expect(described_class.retry_without_delay.retry_without_delay?).to eq(true)
37
+ expect(described_class.ok.retry_without_delay?).to eq(false)
38
+ end
39
+ end
40
+
41
+ describe '#retry_with_delay?' do
42
+ it 'returns true if the response matches, false otherwise' do
43
+ expect(described_class.retry_with_delay.retry_with_delay?).to eq(true)
44
+ expect(described_class.ok.retry_with_delay?).to eq(false)
45
+ end
46
+ end
47
+
48
+ describe '#fail?' do
49
+ it 'returns true if the response matches, false otherwise' do
50
+ expect(described_class.fail.fail?).to eq(true)
51
+ expect(described_class.ok.fail?).to eq(false)
52
+ end
53
+ end
54
+
55
+ describe '#ok?' do
56
+ it 'returns true if the response matches, false otherwise' do
57
+ expect(described_class.ok.ok?).to eq(true)
58
+ expect(described_class.fail.ok?).to eq(false)
59
+ end
60
+ end
61
+
62
+ describe '#to_s' do
63
+ it 'converts the status into a string' do
64
+ expect(described_class.fail.to_s).to eq('fail')
65
+ expect(described_class.ok.to_s).to eq('ok')
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ include TxghQueue
4
+
5
+ describe Supervisor do
6
+ describe '.supervise' do
7
+ it 'provides a handy shortcut to wrap a block with supervising' do
8
+ response = described_class.supervise { raise StandardError }
9
+ expect(response.status).to eq(Status.fail)
10
+ end
11
+ end
12
+
13
+ describe '#execute' do
14
+ it 'handles errors generated by the given block' do
15
+ responder = described_class.new { raise StandardError }
16
+ expect(responder.execute.status).to eq(Status.fail)
17
+ end
18
+
19
+ it 'handles error responses generated by the given block' do
20
+ responder = described_class.new do
21
+ TxghServer::Response.new(401, 'Unauthorized')
22
+ end
23
+
24
+ expect(responder.execute.status).to eq(Status.fail)
25
+ end
26
+
27
+ it 'handles non-error responses generated by the given block' do
28
+ responder = described_class.new do
29
+ TxghServer::Response.new(200, 'Ok')
30
+ end
31
+
32
+ expect(responder.execute.status).to eq(Status.ok)
33
+ end
34
+
35
+ it "raises an error if error handling logic can't be determined" do
36
+ responder = described_class.new { 'unexpected response' }
37
+ expect { responder.execute }.to raise_error(UnexpectedResponse)
38
+ end
39
+ end
40
+ end