govuk_message_queue_consumer 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c236b54beb0a0440047c111caf390c175b9a6a67
4
- data.tar.gz: 6064d29e67a5156cf20d111d8704045cb8816fac
3
+ metadata.gz: f3d0a2e8f69b4d555db92e1251f26b8238f1ad63
4
+ data.tar.gz: c97edff469050cfbf09031625ff3e22f2c573bee
5
5
  SHA512:
6
- metadata.gz: bb3b285c4210e1cc9bd0c42395ab875391aaa1a94d795bcd1a53d005ddcdaea852bf3f0e59c4f846f63abe5a7d74961d6eb5c20d570e2ee4fdc56246c7afade8
7
- data.tar.gz: b62511ec795ad7dcda904577f4f3ab2ba2943fbc27a2b2ba4be6c5c292f5f18c146c92d1390022aa3ba9d89096d7bdca6df2da7c198d2b6495ef365a968409d8
6
+ metadata.gz: 0ad7430c3ed9f12498a91b90c30050737809f0e2c0234e0892a417d7813004c306d68c95992c04def1fa23b987bc6f5652671472685a8a95dcc257f5ec5c15be
7
+ data.tar.gz: b719dadfe75138257d573f91ea758ebf6878ac076971a76dd49012b8de6dd2a35c9c68b559609e1a44148c6ced539c9d8d510c7ad59c95e38241a1903eebd0c6
data/README.md CHANGED
@@ -1,73 +1,140 @@
1
- # GOV.UK Message queue consumer
1
+ # GOV.UK Message Queue Consumer
2
2
 
3
- Standardise the way GOV.UK consumes messages from RabbitMQ.
3
+ Standardises the way GOV.UK consumes messages from [RabbitMQ](https://www.rabbitmq.com/).
4
+ RabbitMQ is a messaging framework that allows applications to broadcast messages
5
+ that can be picked up by other applications.
4
6
 
7
+ On GOV.UK, [publishing-api](https://github.com/alphagov/publishing-api) publishes
8
+ the content-items it receives, so that applications such as
9
+ [email-alert-service](https://github.com/alphagov/email-alert-service) can be
10
+ notified of changes in content.
5
11
 
6
12
  ## Nomenclature
7
13
 
8
- - **Message queue**: a method of providing asynchronous interprocess
9
- communication
10
-
14
+ ![A graph showing the message flow](docs/graph.png)
15
+
16
+ - **Producer**: an application that sends messages RabbitMQ. On GOV.UK this could
17
+ be [publishing-api](https://github.com/alphagov/publishing-api).
18
+ - **Message**: an object sent over RabbitMQ. It consists of a _payload_ and
19
+ _headers_. In the case of the publishing-api the payload is a
20
+ [content item](https://github.com/alphagov/govuk-content-schemas).
21
+ - **Consumer**: the app that receives the messages and does something with them.
22
+ On GOV.UK, these could be [email-alert-service](https://github.com/alphagov/email-alert-service)
23
+ and [content-register](https://github.com/alphagov/content-register).
24
+ - **Exchange**: in RabbitMQ's model, producers send messages to an _exchange_.
25
+ Consumers can create a Queue that listens to the exchange, instead of
26
+ subscribing to the exchange directly. This is done so that the queue can buffer
27
+ any messages and we can make sure all messages get delivered to the consumer.
28
+ - **Queue**: a queue listens to an exchange. In most cases the queue will listen
29
+ to all messages, but it's also possible to listen to a specific pattern.
30
+ - **Processor**: the specific class that processes a message.
11
31
 
12
32
  ## Technical documentation
13
33
 
14
- This is a ruby gem that deals with the boiler plate code of connecting,
15
- subscribing, etc, to [RabbitMQ](https://www.rabbitmq.com/).
16
-
17
- The user of this gem is left the task of supplying their rabbitmq infrastructure
18
- configuration and an instance of a class that processes messages.
34
+ This is a ruby gem that deals with the boiler plate code of communicating with
35
+ [RabbitMQ](https://www.rabbitmq.com/). The user of this gem is left the task of
36
+ supplying the configuration and a class that processes messages.
19
37
 
20
- The message format received by the message processor is found in
21
- `lib/govuk_message_queue_consumer/message.rb`
38
+ This gem is auto-released with [gem_publisher](https://github.com/alphagov/gem_publisher).
39
+ To release a new version, simply raise a pull request with the version number
40
+ incremented.
22
41
 
23
42
  ### Dependencies
24
43
 
25
- - **bunny**: to interact with RabbitMQ
26
- - **activesupport**: use `with_indifferent_access` for Bunny
44
+ - The [Bunny](https://github.com/ruby-amqp/bunny) gem: to interact with RabbitMQ.
45
+
46
+ ## Usage
27
47
 
48
+ For an example on how to implement a message queue consumer, see [alphagov/panopticon#307](https://github.com/alphagov/panopticon/pull/307/files).
28
49
 
29
- ### Running the application
50
+ Add the gem:
51
+
52
+ ```ruby
53
+ # Gemfile
54
+ gem "govuk_message_queue_consumer", "~> 2.0.0"
55
+ ```
30
56
 
31
- We recommend creating a rake task like the following example:
57
+ Add a rake task like the following example:
32
58
 
33
59
  ```ruby
60
+ # lib/tasks/message_queue.rake
34
61
  namespace :message_queue do
35
62
  desc "Run worker to consume messages from rabbitmq"
36
63
  task consumer: :environment do
37
- config = get_rabbitmq_configuration_hash
38
- # ^ eg YAML.load_file(Rails.root.join('config', 'rabbitmq.yml'))[Rails.env]
39
- GovukMessageQueueConsumer::Consumer.new(config, MyProcessor.new).run
64
+ GovukMessageQueueConsumer::Consumer.new(
65
+ queue_name: "some-queue",
66
+ exchange_name: "some-exchange",
67
+ processor: MyProcessor.new
68
+ ).run
40
69
  end
41
70
  end
42
71
  ```
43
72
 
44
- `govuk_message_queue_consumer` expects configuration and a processor to be supplied:
73
+ The consumer expects a number of environment variables to be present. On GOV.UK,
74
+ these should be set up in puppet.
75
+
76
+ ```
77
+ RABBITMQ_HOSTS=rabbitmq1.example.com,rabbitmq2.example.com
78
+ RABBITMQ_VHOST=/
79
+ RABBITMQ_USER=a_user
80
+ RABBITMQ_PASSWORD=a_super_secret
81
+ ```
82
+
83
+ Define a class that will process the messages:
45
84
 
46
85
  ```ruby
47
- # example configuration. Could be stored in YAML if preferred
48
- config = {
49
- host: 'localhost',
50
- port: 5672,
51
- user: rabbitmq_user,
52
- pass: rabbitmq_pass,
53
- recover_from_connection_close: true,
54
- exchange: my_exchange,
55
- queue: my_queue,
56
- }
57
-
58
- # example message processor
86
+ # eg. app/queue_consumers/my_processor.rb
59
87
  class MyProcessor
60
88
  def process(message)
61
- message.ack
89
+ # do something cool
62
90
  end
63
91
  end
64
92
  ```
65
93
 
66
- #### Testing your processor
94
+ The worker should also be added to the Procfile to run in production:
95
+
96
+ ```
97
+ # Procfile
98
+ worker: bundle exec rake message_queue:consumer
99
+ ```
100
+
101
+ Because you need the environment variables when running the consumer, you should use
102
+ `govuk_setenv` to run your app in development:
103
+
104
+ ```
105
+ $ govuk_setenv app-name bundle exec rake message_queue:consumer
106
+ ```
107
+
108
+ ### Processing a message
109
+
110
+ Once you receive a message, you *must* tell RabbitMQ once you've processed it. This
111
+ is called _acking_. You can also _discard_ the message, or _retry_ it.
112
+
113
+ ```ruby
114
+ class MyProcessor
115
+ def process(message)
116
+ result = do_something_with(message)
117
+
118
+ if result.ok?
119
+ # Ack the message when it has been processed correctly.
120
+ message.ack
121
+ elsif result.failed_temporarily?
122
+ # Retry the message to make RabbitMQ send the message again later.
123
+ message.retry
124
+ elsif result.failed_permanently?
125
+ # Discard the message when it can't be processed.
126
+ message.discard
127
+ end
128
+ end
129
+ end
130
+ ```
131
+
132
+ ### Testing your processor
67
133
 
68
134
  This gem provides a test helper for your processor.
69
135
 
70
136
  ```ruby
137
+ # eg. spec/queue_consumers/my_processor_spec.rb
71
138
  require 'test_helper'
72
139
  require 'govuk_message_queue_consumer/test_helpers'
73
140
 
@@ -76,7 +143,28 @@ describe MyProcessor do
76
143
  end
77
144
  ```
78
145
 
79
- This will verify that your processor class implements the correct methods. You should add your own tests to verify its behaviour.
146
+ This will verify that your processor class implements the correct methods. You
147
+ should add your own tests to verify its behaviour.
148
+
149
+ You can use `GovukMessageQueueConsumer::MockMessage` to test the processor
150
+ behaviour. When using the mock, you can verify it acknowledged, retried or
151
+ discarded. For example, with `MyProcessor` above:
152
+
153
+ ```ruby
154
+ it "acks incoming messages" do
155
+ message = GovukMessageQueueConsumer::MockMessage.new
156
+
157
+ MyProcessor.new.process(message)
158
+
159
+ expect(message).to be_acked
160
+
161
+ # or if you use minitest:
162
+ assert message.acked?
163
+ end
164
+ ```
165
+
166
+ For more test cases [see the spec for the mock itself](/spec/mock_message_spec.rb).
167
+
80
168
 
81
169
  ### Running the test suite
82
170
 
@@ -84,12 +172,19 @@ This will verify that your processor class implements the correct methods. You s
84
172
  bundle exec rake spec
85
173
  ```
86
174
 
175
+ ## Further reading
176
+
177
+ - [Bunny](https://github.com/ruby-amqp/bunny) is the RabbitMQ client we use.
178
+ - [The Bunny Guides](http://rubybunny.info/articles/guides.html) explain all
179
+ AMQP concepts really well.
180
+ - The [Opsmanual](https://github.gds/pages/gds/opsmanual/2nd-line/nagios.html?highlight=rabbitmq#rabbitmq-checks)
181
+ documents the usage of "heartbeat" messages, which this gem also supports.
87
182
 
88
183
  ## Licence
89
184
 
90
185
  [MIT License](LICENCE)
91
186
 
92
-
93
187
  ## Versioning policy
94
188
 
95
- [Semantic versioning](http://semver.org/spec/v2.0.0.html)
189
+ We follow [Semantic versioning](http://semver.org/spec/v2.0.0.html). Check the
190
+ [CHANGELOG](CHANGELOG.md) for changes.
@@ -1,22 +1,28 @@
1
- require 'active_support/core_ext/hash/indifferent_access'
2
1
  require 'bunny'
3
2
 
4
3
  module GovukMessageQueueConsumer
5
4
  class Consumer
6
- def initialize(config, processor)
7
- @processor = HeartbeatProcessor.new(processor)
8
-
9
- @config = config.with_indifferent_access
10
- @queue_name = @config.fetch(:queue)
11
- @bindings = { @config.fetch(:exchange) => "#" }
12
- @connection = Bunny.new(@config[:connection].symbolize_keys)
13
- @connection.start
5
+ # Only fetch one message at a time on the channel.
6
+ #
7
+ # By default, queues will grab messages eagerly, which reduces latency.
8
+ # However, that also means that if multiple workers are running one worker
9
+ # can starve another of work. We're not expecting a high throughput on this
10
+ # queue, and a small bit of latency isn't a problem, so we fetch one at a
11
+ # time to share the work evenly.
12
+ NUMBER_OF_MESSAGES_TO_PREFETCH = 1
13
+
14
+ def initialize(queue_name:, exchange_name:, processor:, routing_key: '#')
15
+ @queue_name = queue_name
16
+ @exchange_name = exchange_name
17
+ @processor = processor
18
+ @routing_key = routing_key
14
19
  end
15
20
 
16
21
  def run
17
- queue.subscribe(:block => true, :manual_ack => true) do |delivery_info, headers, payload|
22
+ queue.subscribe(block: true, manual_ack: true) do |delivery_info, headers, payload|
18
23
  begin
19
- @processor.process(Message.new(delivery_info, headers, payload))
24
+ message = Message.new(payload, headers, delivery_info)
25
+ processor_chain.process(message)
20
26
  rescue Exception => e
21
27
  $stderr.puts "rabbitmq_consumer: aborting due to unhandled exception in processor #{e.class}: #{e.message}"
22
28
  exit(1) # ensure rabbitmq requeues outstanding messages
@@ -24,21 +30,35 @@ module GovukMessageQueueConsumer
24
30
  end
25
31
  end
26
32
 
27
- private
33
+ private
34
+
35
+ def processor_chain
36
+ @processor_chain ||= HeartbeatProcessor.new(JSONProcessor.new(@processor))
37
+ end
28
38
 
29
39
  def queue
30
- @queue ||= setup_queue
40
+ @queue ||= begin
41
+ channel.prefetch(NUMBER_OF_MESSAGES_TO_PREFETCH)
42
+ queue = channel.queue(@queue_name, durable: true)
43
+ queue.bind(exchange, routing_key: @routing_key)
44
+ queue
45
+ end
46
+ end
47
+
48
+ def exchange
49
+ @exchange ||= channel.topic(@exchange_name, passive: true)
50
+ end
51
+
52
+ def channel
53
+ @channel ||= connection.create_channel
31
54
  end
32
55
 
33
- def setup_queue
34
- @channel = @connection.create_channel
35
- @channel.prefetch(1) # only one unacked message at a time
36
- queue = @channel.queue(@queue_name, :durable => true)
37
- @bindings.each do |exchange_name, routing_key|
38
- exchange = @channel.topic(exchange_name, :passive => true)
39
- queue.bind(exchange, :routing_key => routing_key)
56
+ def connection
57
+ @connection ||= begin
58
+ new_connection = Bunny.new(RabbitMQConfig.new.from_environment)
59
+ new_connection.start
60
+ new_connection
40
61
  end
41
- queue
42
62
  end
43
63
  end
44
64
  end
@@ -0,0 +1,20 @@
1
+ module GovukMessageQueueConsumer
2
+ class JSONProcessor
3
+ JSON_FORMAT = "application/json".freeze
4
+
5
+ def initialize(next_processor)
6
+ @next_processor = next_processor
7
+ end
8
+
9
+ def process(message)
10
+ if message.headers.content_type == JSON_FORMAT
11
+ message.payload = JSON.parse(message.payload)
12
+ end
13
+
14
+ @next_processor.process(message)
15
+ rescue JSON::ParserError => e
16
+ Airbrake.notify_or_ignore(e) if defined?(Airbrake)
17
+ message.discard
18
+ end
19
+ end
20
+ end
@@ -3,16 +3,12 @@ require 'json'
3
3
  module GovukMessageQueueConsumer
4
4
  # Client code will receive an instance of this
5
5
  class Message
6
- def initialize(delivery_info, headers, payload)
7
- @delivery_info = delivery_info
8
- @headers = headers
9
- @body = payload
10
- end
6
+ attr_accessor :delivery_info, :headers, :payload
11
7
 
12
- attr_reader :delivery_info, :headers, :body
13
-
14
- def body_data
15
- @body_data ||= JSON.parse(@body)
8
+ def initialize(payload, headers, delivery_info)
9
+ @payload = payload
10
+ @headers = headers
11
+ @delivery_info = delivery_info
16
12
  end
17
13
 
18
14
  def ack
@@ -0,0 +1,29 @@
1
+ class RabbitMQConfig
2
+ class ConfigurationError < StandardError
3
+ end
4
+
5
+ def from_environment
6
+ {
7
+ hosts: fetch("RABBITMQ_HOSTS").split(','),
8
+ vhost: fetch("RABBITMQ_VHOST"),
9
+ user: fetch("RABBITMQ_USER"),
10
+ pass: fetch("RABBITMQ_PASSWORD"),
11
+ recover_from_connection_close: true,
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def fetch(variable_name)
18
+ ENV[variable_name] || raise_error(variable_name)
19
+ end
20
+
21
+ def raise_error(variable_name)
22
+ raise ConfigurationError, <<-err
23
+ The environment variable #{variable_name} is not set. If you are in test
24
+ mode, make sure you set the correct vars in your helpers. If you get this
25
+ error in development, make sure you run rails or rake with `govuk_setenv`
26
+ and puppet is up to date.
27
+ err
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module GovukMessageQueueConsumer
2
+ class MockMessage < Message
3
+ attr_reader :acked, :retried, :discarded
4
+
5
+ alias :acked? :acked
6
+ alias :discarded? :discarded
7
+ alias :retried? :retried
8
+
9
+ def initialize(payload = {}, headers = {}, delivery_info = {})
10
+ @payload = payload
11
+ @headers = OpenStruct.new(headers)
12
+ @delivery_info = OpenStruct.new(delivery_info)
13
+ end
14
+
15
+ def ack
16
+ @acked = true
17
+ end
18
+
19
+ def retry
20
+ @retried = true
21
+ end
22
+
23
+ def discard
24
+ @discarded = true
25
+ end
26
+ end
27
+ end
@@ -1,9 +1,11 @@
1
- RSpec.shared_examples "a message queue processor" do
2
- it "implements #process" do
3
- expect(subject).to respond_to(:process)
4
- end
1
+ if defined?(RSpec)
2
+ RSpec.shared_examples "a message queue processor" do
3
+ it "implements #process" do
4
+ expect(subject).to respond_to(:process)
5
+ end
5
6
 
6
- it "accepts 1 argument for #process" do
7
- expect(subject.method(:process).arity).to eq(1)
7
+ it "accepts 1 argument for #process" do
8
+ expect(subject.method(:process).arity).to eq(1)
9
+ end
8
10
  end
9
11
  end
@@ -0,0 +1,12 @@
1
+ module GovukMessageQueueConsumer
2
+ class TestConsumer < Consumer
3
+ def publish_message(payload, options)
4
+ exchange.publish(payload, options)
5
+ end
6
+
7
+ # call after integration tests finish
8
+ def delete_queue
9
+ queue.delete
10
+ end
11
+ end
12
+ end
@@ -1 +1,3 @@
1
1
  require 'govuk_message_queue_consumer/test_helpers/shared_examples'
2
+ require 'govuk_message_queue_consumer/test_helpers/mock_message'
3
+ require 'govuk_message_queue_consumer/test_helpers/test_consumer'
@@ -1,3 +1,3 @@
1
1
  module GovukMessageQueueConsumer
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -1,4 +1,6 @@
1
1
  require 'govuk_message_queue_consumer/version'
2
2
  require 'govuk_message_queue_consumer/heartbeat_processor'
3
+ require 'govuk_message_queue_consumer/json_processor'
3
4
  require 'govuk_message_queue_consumer/message'
4
5
  require 'govuk_message_queue_consumer/consumer'
6
+ require 'govuk_message_queue_consumer/rabbitmq_config'
@@ -1,48 +1,35 @@
1
1
  require_relative 'spec_helper'
2
2
 
3
3
  describe Consumer do
4
-
5
- let(:queue) { instance_double('Bunny::Queue', bind: nil, subscribe: message_values) }
4
+ let(:queue) { instance_double('Bunny::Queue', bind: nil, subscribe: '') }
6
5
  let(:channel) { instance_double('Bunny::Channel', queue: queue, prefetch: nil, topic: nil) }
7
6
  let(:rabbitmq_connecton) { instance_double("Bunny::Session", start: nil, create_channel: channel) }
8
7
  let(:client_processor) { instance_double('Client::Processor') }
9
8
 
10
9
  before do
10
+ stub_environment_variables!
11
11
  allow(Bunny).to receive(:new).and_return(rabbitmq_connecton)
12
12
  end
13
13
 
14
- describe "constructing an instance" do
15
- let(:rabbitmq_connecton) { instance_double("Bunny::Session", start: nil, create_channel: channel) }
16
- let(:client_processor) { instance_double('Client::Processor') }
17
-
18
- it "passes the client processor to the Heartbeat Processor" do
19
- expect(HeartbeatProcessor).to receive(:new).with(client_processor)
20
-
21
- Consumer.new(rabbitmq_config, client_processor)
22
- end
23
-
24
- it "connects to rabbitmq" do
25
- expected_options = rabbitmq_config['connection'].symbolize_keys # Bunny requires the keys to be symbols
26
- expect(Bunny).to receive(:new).with(expected_options).and_return(rabbitmq_connecton)
27
- expect(rabbitmq_connecton).to receive(:start)
14
+ describe "running the consumer" do
15
+ it "binds the queue to the all-routing key" do
16
+ expect(queue).to receive(:bind).with(nil, { routing_key: "#" })
28
17
 
29
- Consumer.new(rabbitmq_config, client_processor)
18
+ Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor).run
30
19
  end
31
- end
32
20
 
33
- describe "running the consumer" do
34
- it "binds the queue" do
35
- expect(queue).to receive(:bind)
21
+ it "binds the queue to a custom routing key" do
22
+ expect(queue).to receive(:bind).with(nil, { routing_key: "*.major" })
36
23
 
37
- Consumer.new(rabbitmq_config, client_processor).run
24
+ Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor, routing_key: "*.major").run
38
25
  end
39
26
 
40
27
  it "calls the heartbeat processor when subscribing to messages" do
41
- expect(queue).to receive(:subscribe).and_yield(*message_values)
42
- expect(Message).to receive(:new).with(*message_values)
28
+ expect(queue).to receive(:subscribe).and_yield(:delivery_info_object, :headers, "payload")
29
+ expect(Message).to receive(:new).with("payload", :headers, :delivery_info_object)
43
30
  expect_any_instance_of(HeartbeatProcessor).to receive(:process)
44
31
 
45
- Consumer.new(rabbitmq_config, client_processor).run
32
+ Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor).run
46
33
  end
47
34
  end
48
35
  end
@@ -0,0 +1,33 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe JSONProcessor do
4
+ describe "#process" do
5
+ it "parses the payload string" do
6
+ next_processor = double("next_processor", process: "ha")
7
+ message = MockMessage.new('{"some":"json"}', { content_type: "application/json" })
8
+
9
+ JSONProcessor.new(next_processor).process(message)
10
+
11
+ expect(message.payload).to eql({ "some" => "json" })
12
+ expect(next_processor).to have_received(:process)
13
+ end
14
+
15
+ it "discards messages with JSON errors" do
16
+ message = MockMessage.new('{"some" "json"}', { content_type: "application/json" })
17
+
18
+ JSONProcessor.new(double).process(message)
19
+
20
+ expect(message).to be_discarded
21
+ end
22
+
23
+ it "doesn't parse non-JSON message" do
24
+ next_processor = double("next_processor", process: "ha")
25
+ message = MockMessage.new('<SomeXML></SomeXML>', { content_type: "application/xml" })
26
+
27
+ JSONProcessor.new(next_processor).process(message)
28
+
29
+ expect(message.payload).to eql('<SomeXML></SomeXML>')
30
+ expect(next_processor).to have_received(:process)
31
+ end
32
+ end
33
+ end
data/spec/message_spec.rb CHANGED
@@ -4,25 +4,23 @@ describe Message do
4
4
  let(:mock_channel) { instance_double("Channel") }
5
5
  let(:delivery_info) { instance_double("DeliveryInfo", :channel => mock_channel, :delivery_tag => "a_tag") }
6
6
  let(:headers) { instance_double("Headers") }
7
- let(:body) { {"foo" => "bar"}.to_json }
8
- let(:message) { Message.new(delivery_info, headers, body) }
9
-
10
- it "json decodes the body" do
11
- expect(message.body_data).to eq("foo" => "bar")
12
- end
7
+ let(:message) { Message.new({ "a" => "payload" }, headers, delivery_info) }
13
8
 
14
9
  it "ack sends an ack to the channel" do
15
10
  expect(mock_channel).to receive(:ack).with("a_tag")
11
+
16
12
  message.ack
17
13
  end
18
14
 
19
15
  it "retry sends a reject to the channel with requeue set" do
20
16
  expect(mock_channel).to receive(:reject).with("a_tag", true)
17
+
21
18
  message.retry
22
19
  end
23
20
 
24
21
  it "reject sends a reject to the channel without requeue set" do
25
22
  expect(mock_channel).to receive(:reject).with("a_tag", false)
23
+
26
24
  message.discard
27
25
  end
28
26
  end
@@ -0,0 +1,43 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/govuk_message_queue_consumer/test_helpers/mock_message'
3
+
4
+ describe GovukMessageQueueConsumer::MockMessage do
5
+ describe '#methods' do
6
+ it "implements the same methods as Message" do
7
+ mock = MockMessage.new
8
+ real = Message.new(double, double, double)
9
+
10
+ expect(real.methods - mock.methods).to be_empty
11
+ end
12
+ end
13
+
14
+ describe '#ack' do
15
+ it "marks the message as acked" do
16
+ message = MockMessage.new
17
+
18
+ message.ack
19
+
20
+ expect(message).to be_acked
21
+ end
22
+ end
23
+
24
+ describe '#retry' do
25
+ it "marks the message as retried" do
26
+ message = MockMessage.new
27
+
28
+ message.retry
29
+
30
+ expect(message).to be_retried
31
+ end
32
+ end
33
+
34
+ describe '#discard' do
35
+ it "marks the message as discarded" do
36
+ message = MockMessage.new
37
+
38
+ message.discard
39
+
40
+ expect(message).to be_discarded
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RabbitMQConfig do
4
+ describe ".from_environment" do
5
+ it "connects to rabbitmq with the correct environment variables" do
6
+ ENV["RABBITMQ_HOSTS"] = "server-one,server-two"
7
+ ENV["RABBITMQ_VHOST"] = "/"
8
+ ENV["RABBITMQ_USER"] = "my_user"
9
+ ENV["RABBITMQ_PASSWORD"] = "my_pass"
10
+
11
+ expect(RabbitMQConfig.new.from_environment).to eql({
12
+ hosts: ["server-one", "server-two"],
13
+ vhost: "/",
14
+ user: "my_user",
15
+ pass: "my_pass",
16
+ recover_from_connection_close: true,
17
+ })
18
+ end
19
+
20
+ it "provides a friendly error message when a variable is missing" do
21
+ ENV["RABBITMQ_HOSTS"] = nil
22
+
23
+ expect { RabbitMQConfig.new.from_environment }.to raise_error(RabbitMQConfig::ConfigurationError)
24
+ end
25
+ end
26
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,23 +3,11 @@ require_relative '../lib/govuk_message_queue_consumer'
3
3
  include GovukMessageQueueConsumer
4
4
 
5
5
  module TestHelpers
6
- def rabbitmq_config
7
- {
8
- "connection" => {
9
- "hosts" => ["rabbitmq1.example.com", "rabbitmq2.example.com"],
10
- "port" => 5672,
11
- "vhost" => "/",
12
- "user" => "a_user",
13
- "pass" => "super secret",
14
- "recover_from_connection_close" => true,
15
- },
16
- "queue" => "content_register",
17
- "exchange" => "published_documents",
18
- }
19
- end
20
-
21
- def message_values
22
- [:delivery_info1, :headers1, "message1_body"]
6
+ def stub_environment_variables!
7
+ ENV["RABBITMQ_HOSTS"] ||= ""
8
+ ENV["RABBITMQ_VHOST"] ||= "/"
9
+ ENV["RABBITMQ_USER"] ||= "/"
10
+ ENV["RABBITMQ_PASSWORD"] ||= "/"
23
11
  end
24
12
  end
25
13
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_message_queue_consumer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-21 00:00:00.000000000 Z
11
+ date: 2015-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.2.0
27
- - !ruby/object:Gem::Dependency
28
- name: activesupport
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '4.0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '4.0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: gem_publisher
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -93,13 +79,20 @@ files:
93
79
  - lib/govuk_message_queue_consumer.rb
94
80
  - lib/govuk_message_queue_consumer/consumer.rb
95
81
  - lib/govuk_message_queue_consumer/heartbeat_processor.rb
82
+ - lib/govuk_message_queue_consumer/json_processor.rb
96
83
  - lib/govuk_message_queue_consumer/message.rb
84
+ - lib/govuk_message_queue_consumer/rabbitmq_config.rb
97
85
  - lib/govuk_message_queue_consumer/test_helpers.rb
86
+ - lib/govuk_message_queue_consumer/test_helpers/mock_message.rb
98
87
  - lib/govuk_message_queue_consumer/test_helpers/shared_examples.rb
88
+ - lib/govuk_message_queue_consumer/test_helpers/test_consumer.rb
99
89
  - lib/govuk_message_queue_consumer/version.rb
100
90
  - spec/consumer_spec.rb
101
91
  - spec/heartbeat_processor_spec.rb
92
+ - spec/json_processor_spec.rb
102
93
  - spec/message_spec.rb
94
+ - spec/mock_message_spec.rb
95
+ - spec/rabbitmq_config_spec.rb
103
96
  - spec/spec_helper.rb
104
97
  homepage: https://github.com/alphagov/govuk_message_queue_consumer
105
98
  licenses: []
@@ -129,3 +122,6 @@ test_files:
129
122
  - spec/spec_helper.rb
130
123
  - spec/message_spec.rb
131
124
  - spec/consumer_spec.rb
125
+ - spec/mock_message_spec.rb
126
+ - spec/json_processor_spec.rb
127
+ - spec/rabbitmq_config_spec.rb