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 '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