pheme 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +32 -0
- data/circle.yml +3 -0
- data/lib/pheme.rb +11 -0
- data/lib/pheme/configuration.rb +34 -0
- data/lib/pheme/logger.rb +7 -0
- data/lib/pheme/message_handler.rb +13 -0
- data/lib/pheme/queue_poller.rb +54 -0
- data/lib/pheme/topic_publisher.rb +18 -0
- data/lib/pheme/version.rb +3 -0
- data/pheme.gemspec +29 -0
- data/spec/configuration_spec.rb +22 -0
- data/spec/message_handler_spec.rb +12 -0
- data/spec/queue_poller_spec.rb +111 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/example_message_handler.rb +10 -0
- data/spec/support/example_publisher.rb +10 -0
- data/spec/support/example_queue_poller.rb +10 -0
- data/spec/topic_publisher_spec.rb +18 -0
- data/spec/version_spec.rb +5 -0
- metadata +146 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: b5feb32f5663e1118ccd4307e0552cb85e513a10
         | 
| 4 | 
            +
              data.tar.gz: 80cad78d39a25a965129eedb7411cbc96ccffc6b
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 3348f6ea4828665392ce7770e577ba35fb4d07d7d5409c38a1dde176b9fc194a2e3315c9a9eef4f1445ef9c0875acd3ee62cb778233eb5758300ebbfcaeff581
         | 
| 7 | 
            +
              data.tar.gz: 2d608692cb8dba727888309160fba22f84a05d2121ed00251186f0fa980ff4380aa696812cfa2fa7c411d1e1749bffe64a8a3b988f061aa513d8bf78827ee1a4
         | 
    
        data/.gitignore
    ADDED
    
    | @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            *.gem
         | 
| 2 | 
            +
            *.rbc
         | 
| 3 | 
            +
            /.config
         | 
| 4 | 
            +
            /coverage/
         | 
| 5 | 
            +
            /InstalledFiles
         | 
| 6 | 
            +
            /pkg/
         | 
| 7 | 
            +
            /spec/reports/
         | 
| 8 | 
            +
            /spec/examples.txt
         | 
| 9 | 
            +
            /test/tmp/
         | 
| 10 | 
            +
            /test/version_tmp/
         | 
| 11 | 
            +
            /tmp/
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ## Specific to RubyMotion:
         | 
| 14 | 
            +
            .dat*
         | 
| 15 | 
            +
            .repl_history
         | 
| 16 | 
            +
            build/
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ## Documentation cache and generated files:
         | 
| 19 | 
            +
            /.yardoc/
         | 
| 20 | 
            +
            /_yardoc/
         | 
| 21 | 
            +
            /doc/
         | 
| 22 | 
            +
            /rdoc/
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ## Environment normalization:
         | 
| 25 | 
            +
            /.bundle/
         | 
| 26 | 
            +
            /vendor/bundle
         | 
| 27 | 
            +
            /lib/bundler/man/
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            # for a library or gem, you might want to ignore these files since the code is
         | 
| 30 | 
            +
            # intended to run in multiple environments; otherwise, check them in:
         | 
| 31 | 
            +
            Gemfile.lock
         | 
| 32 | 
            +
            .ruby-version
         | 
| 33 | 
            +
            .ruby-gemset
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
         | 
| 36 | 
            +
            .rvmrc
         | 
    
        data/.rspec
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2016 Wealthsimple
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in all
         | 
| 13 | 
            +
            copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         | 
| 21 | 
            +
            SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # pheme [](https://circleci.com/gh/wealthsimple/pheme)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Ruby SNS publisher + SQS poller & message handler
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## installation & config
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ```ruby
         | 
| 8 | 
            +
            # Gemfile
         | 
| 9 | 
            +
            gem 'pheme'
         | 
| 10 | 
            +
            ```
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ```ruby
         | 
| 13 | 
            +
            # Initializer
         | 
| 14 | 
            +
            aws_config = {
         | 
| 15 | 
            +
              credentials: Aws::Credentials.new('YOUR_ACCESS_KEY_ID', 'YOUR_SECRET_ACCESS_KEY'),
         | 
| 16 | 
            +
              region: 'us-east-1',
         | 
| 17 | 
            +
            }
         | 
| 18 | 
            +
            AWS_SNS_CLIENT = Aws::SNS::Client.new(aws_config)
         | 
| 19 | 
            +
            AWS_SQS_CLIENT = Aws::SQS::Client.new(aws_config)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Pheme.configure do |config|
         | 
| 22 | 
            +
              config.sqs_client = AWS_SQS_CLIENT
         | 
| 23 | 
            +
              config.sns_client = AWS_SNS_CLIENT
         | 
| 24 | 
            +
              config.logger = Logger.new(STDOUT) # Optionally replace with your app logger, e.g. `Rails.logger`
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
            ```
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            # usage
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            See https://github.com/wealthsimple/pheme/tree/master/spec/support for example implementations of each class.
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            TODO: write better usage instructions.
         | 
    
        data/circle.yml
    ADDED
    
    
    
        data/lib/pheme.rb
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            require 'active_support/all'
         | 
| 2 | 
            +
            require 'recursive-open-struct'
         | 
| 3 | 
            +
            require 'aws-sdk'
         | 
| 4 | 
            +
            require 'securerandom'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'pheme/version'
         | 
| 7 | 
            +
            require 'pheme/configuration'
         | 
| 8 | 
            +
            require 'pheme/logger'
         | 
| 9 | 
            +
            require 'pheme/topic_publisher'
         | 
| 10 | 
            +
            require 'pheme/message_handler'
         | 
| 11 | 
            +
            require 'pheme/queue_poller'
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Pheme
         | 
| 2 | 
            +
              class << self
         | 
| 3 | 
            +
                attr_writer :configuration
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def self.configuration
         | 
| 7 | 
            +
                @configuration ||= Configuration.new
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def self.configure
         | 
| 11 | 
            +
                yield(configuration)
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def self.reset_configuration!
         | 
| 15 | 
            +
                @configuration = Configuration.new
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              class Configuration
         | 
| 19 | 
            +
                ATTRIBUTES = [:sns_client, :sqs_client, :logger]
         | 
| 20 | 
            +
                attr_accessor *ATTRIBUTES
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def initialize
         | 
| 23 | 
            +
                  @logger ||= Logger.new(STDOUT)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def validate!
         | 
| 27 | 
            +
                  ATTRIBUTES.each do |attribute|
         | 
| 28 | 
            +
                    raise "Invalid or missing configuration for #{attribute}"  unless send(attribute).present?
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                  raise "sns_client must be a Aws::SNS::Client"  unless sns_client.is_a?(Aws::SNS::Client)
         | 
| 31 | 
            +
                  raise "sns_client must be a Aws::SQS::Client"  unless sqs_client.is_a?(Aws::SQS::Client)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
    
        data/lib/pheme/logger.rb
    ADDED
    
    
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module Pheme
         | 
| 2 | 
            +
              class QueuePoller
         | 
| 3 | 
            +
                attr_accessor :queue_url, :queue_poller, :connection_pool_block, :poller_configuration
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(queue_url:, connection_pool_block: false, poller_configuration: {})
         | 
| 6 | 
            +
                  @queue_url = queue_url
         | 
| 7 | 
            +
                  @queue_poller = Aws::SQS::QueuePoller.new(queue_url)
         | 
| 8 | 
            +
                  @connection_pool_block = connection_pool_block
         | 
| 9 | 
            +
                  @poller_configuration = poller_configuration.merge({
         | 
| 10 | 
            +
                    wait_time_seconds: 10, # amount of time a long polling receive call can wait for a mesage before receiving a empty response (which will trigger another polling request)
         | 
| 11 | 
            +
                    idle_timeout: 20, # disconnects poller after 20 seconds of idle time
         | 
| 12 | 
            +
                    visibility_timeout: 30, # length of time in seconds that this message will not be visible to other receiving components
         | 
| 13 | 
            +
                    skip_delete: true, # manually delete messages
         | 
| 14 | 
            +
                  })
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def poll
         | 
| 18 | 
            +
                  Pheme.log(:info, "Long-polling for messages on #{queue_url}")
         | 
| 19 | 
            +
                  with_optional_connection_pool_block do
         | 
| 20 | 
            +
                    queue_poller.poll(poller_configuration) do |message|
         | 
| 21 | 
            +
                      begin
         | 
| 22 | 
            +
                        handle(parse_message(message))
         | 
| 23 | 
            +
                        queue_poller.delete_message(message)
         | 
| 24 | 
            +
                      rescue => e
         | 
| 25 | 
            +
                        Pheme.log(:error, "Exception: #{e.inspect}")
         | 
| 26 | 
            +
                        Pheme.log(:error, e.backtrace.join("\n"))
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                  Pheme.log(:info, "Finished long-polling after #{@poller_configuration[:idle_timeout]} seconds.")
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def parse_message(message)
         | 
| 34 | 
            +
                  Pheme.log(:info, "Received JSON payload: #{message.body}")
         | 
| 35 | 
            +
                  body = JSON.parse(message.body)
         | 
| 36 | 
            +
                  parsed_body = JSON.parse(body['Message'])
         | 
| 37 | 
            +
                  RecursiveOpenStruct.new(parsed_body, recurse_over_arrays: true)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def handle(message)
         | 
| 41 | 
            +
                  raise NotImplementedError
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def with_optional_connection_pool_block(&blk)
         | 
| 47 | 
            +
                  if connection_pool_block
         | 
| 48 | 
            +
                    ActiveRecord::Base.connection_pool.with_connection { blk.call }
         | 
| 49 | 
            +
                  else
         | 
| 50 | 
            +
                    blk.call
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Pheme
         | 
| 2 | 
            +
              class TopicPublisher
         | 
| 3 | 
            +
                attr_accessor :topic_arn
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(topic_arn:)
         | 
| 6 | 
            +
                  @topic_arn = topic_arn
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def publish_events
         | 
| 10 | 
            +
                  raise NotImplementedError
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def publish(message)
         | 
| 14 | 
            +
                  Pheme.log(:info, "Publishing to #{topic_arn}: #{message}")
         | 
| 15 | 
            +
                  Pheme.configuration.sns_client.publish(topic_arn: topic_arn, message: message.to_json)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/pheme.gemspec
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require "pheme/version"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Gem::Specification.new do |gem|
         | 
| 8 | 
            +
              gem.name          = "pheme"
         | 
| 9 | 
            +
              gem.version       = Pheme::VERSION
         | 
| 10 | 
            +
              gem.authors       = ["Peter Graham"]
         | 
| 11 | 
            +
              gem.email         = ["peter@wealthsimple.com"]
         | 
| 12 | 
            +
              gem.description   = %q{Ruby AWS SNS publisher + SQS poller & message handler}
         | 
| 13 | 
            +
              gem.summary       = %q{Ruby SNS publisher + SQS poller & message handler}
         | 
| 14 | 
            +
              gem.homepage      = "https://github.com/wealthsimple/pheme"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              gem.files         = `git ls-files`.split($/)
         | 
| 17 | 
            +
              gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
         | 
| 18 | 
            +
              gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
              gem.require_paths = ["lib"]
         | 
| 20 | 
            +
              gem.licenses      = ["MIT"]
         | 
| 21 | 
            +
              gem.required_ruby_version = ">= 2.0.0"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              gem.add_dependency "aws-sdk", "~> 2"
         | 
| 24 | 
            +
              gem.add_dependency "activesupport", "~> 4"
         | 
| 25 | 
            +
              gem.add_dependency "recursive-open-struct", "~> 1"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              gem.add_development_dependency "rspec", "~> 3.4"
         | 
| 28 | 
            +
              gem.add_development_dependency "rspec_junit_formatter", "~> 0.2"
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            describe Pheme do
         | 
| 2 | 
            +
              describe ".configure" do
         | 
| 3 | 
            +
                let(:sns_client) { double }
         | 
| 4 | 
            +
                let(:sqs_client) { double }
         | 
| 5 | 
            +
                let(:custom_logger) { double }
         | 
| 6 | 
            +
                it "sets global configuration" do
         | 
| 7 | 
            +
                  expect(described_class.configuration.sns_client).to be_nil
         | 
| 8 | 
            +
                  expect(described_class.configuration.sqs_client).to be_nil
         | 
| 9 | 
            +
                  expect(described_class.configuration.logger).to be_a(Logger)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  described_class.configure do |config|
         | 
| 12 | 
            +
                    config.sns_client = sns_client
         | 
| 13 | 
            +
                    config.sqs_client = sqs_client
         | 
| 14 | 
            +
                    config.logger = custom_logger
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  expect(described_class.configuration.sns_client).to eq(sns_client)
         | 
| 18 | 
            +
                  expect(described_class.configuration.sqs_client).to eq(sqs_client)
         | 
| 19 | 
            +
                  expect(described_class.configuration.logger).to eq(custom_logger)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            describe Pheme::MessageHandler do
         | 
| 2 | 
            +
              before(:each) { use_default_configuration! }
         | 
| 3 | 
            +
              let(:message) { RecursiveOpenStruct.new(status: "complete") }
         | 
| 4 | 
            +
              subject { ExampleMessageHandler.new(message: message) }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe "#handle" do
         | 
| 7 | 
            +
                it "handles the message correctly" do
         | 
| 8 | 
            +
                  expect(Pheme).to receive(:log).with(:info, "Done")
         | 
| 9 | 
            +
                  subject.handle
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
            end
         | 
| @@ -0,0 +1,111 @@ | |
| 1 | 
            +
            describe Pheme::QueuePoller do
         | 
| 2 | 
            +
              let(:queue_url) { "https://sqs.us-east-1.amazonaws.com/whatever" }
         | 
| 3 | 
            +
              let(:poller) do
         | 
| 4 | 
            +
                poller = double
         | 
| 5 | 
            +
                allow(poller).to receive(:poll).with(kind_of(Hash))
         | 
| 6 | 
            +
                poller
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
              before(:each) do
         | 
| 9 | 
            +
                use_default_configuration!
         | 
| 10 | 
            +
                allow(Aws::SQS::QueuePoller).to receive(:new) { poller }
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              describe "#poll" do
         | 
| 14 | 
            +
                before(:each) do
         | 
| 15 | 
            +
                  module ActiveRecord
         | 
| 16 | 
            +
                    class Base
         | 
| 17 | 
            +
                      def self.connection_pool
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                context "with connection pool block" do
         | 
| 24 | 
            +
                  let(:mock_connection_pool) { double }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  before(:each) do
         | 
| 27 | 
            +
                    allow(ActiveRecord::Base).to receive(:connection_pool) { mock_connection_pool }
         | 
| 28 | 
            +
                    allow(mock_connection_pool).to receive(:with_connection).and_yield
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  subject { ExampleQueuePoller.new(queue_url: queue_url, connection_pool_block: true) }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  it "uses the connection pool block" do
         | 
| 34 | 
            +
                    expect(mock_connection_pool).to receive(:with_connection)
         | 
| 35 | 
            +
                    subject.poll
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context "without connection pool block" do
         | 
| 40 | 
            +
                  subject { ExampleQueuePoller.new(queue_url: queue_url) }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  it "does not call ActiveRecord" do
         | 
| 43 | 
            +
                    expect(ActiveRecord::Base).not_to receive(:connection_pool)
         | 
| 44 | 
            +
                    subject.poll
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                context "when a valid message is yielded" do
         | 
| 49 | 
            +
                  let(:message_body) do
         | 
| 50 | 
            +
                    {
         | 
| 51 | 
            +
                      id: "id-123",
         | 
| 52 | 
            +
                      status: "complete",
         | 
| 53 | 
            +
                    }
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                  let(:message) do
         | 
| 56 | 
            +
                    message = double
         | 
| 57 | 
            +
                    allow(message).to receive(:body) do
         | 
| 58 | 
            +
                      {Message: message_body.to_json,}.to_json
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                    message
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  before(:each) do
         | 
| 63 | 
            +
                    allow(poller).to receive(:poll).and_yield(message)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  subject { ExampleQueuePoller.new(queue_url: queue_url) }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  it "handles the message" do
         | 
| 69 | 
            +
                    expect(ExampleMessageHandler).to receive(:new).with(message: RecursiveOpenStruct.new(message_body))
         | 
| 70 | 
            +
                    subject.poll
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  it "deletes the message from the queue" do
         | 
| 74 | 
            +
                    expect(poller).to receive(:delete_message).with(message)
         | 
| 75 | 
            +
                    subject.poll
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                context "when an invalid message is yielded" do
         | 
| 80 | 
            +
                  let(:message_body) do
         | 
| 81 | 
            +
                    {
         | 
| 82 | 
            +
                      id: "id-123",
         | 
| 83 | 
            +
                      status: "unknown-abc",
         | 
| 84 | 
            +
                    }
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                  let(:message) do
         | 
| 87 | 
            +
                    message = double
         | 
| 88 | 
            +
                    allow(message).to receive(:body) do
         | 
| 89 | 
            +
                      {Message: message_body.to_json}.to_json
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
                    message
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                  before(:each) do
         | 
| 94 | 
            +
                    allow(poller).to receive(:poll).and_yield(message)
         | 
| 95 | 
            +
                    allow(Pheme).to receive(:log)
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  subject { ExampleQueuePoller.new(queue_url: queue_url) }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  it "logs the error" do
         | 
| 101 | 
            +
                    subject.poll
         | 
| 102 | 
            +
                    expect(Pheme).to have_received(:log).with(:error, "Exception: #<ArgumentError: Unknown message status: unknown-abc>")
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  it "does not delete the message from the queue" do
         | 
| 106 | 
            +
                    expect(poller).not_to receive(:delete_message)
         | 
| 107 | 
            +
                    subject.poll
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'rspec'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require './lib/pheme'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Dir["./spec/support/**/*.rb"].each { |f| require f }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            RSpec.configure do |config|
         | 
| 8 | 
            +
              config.filter_run :focus
         | 
| 9 | 
            +
              config.run_all_when_everything_filtered = true
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              config.before(:each) do
         | 
| 12 | 
            +
                Pheme.reset_configuration!
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            def use_default_configuration!
         | 
| 17 | 
            +
              Pheme.configure do |config|
         | 
| 18 | 
            +
                config.sqs_client = double
         | 
| 19 | 
            +
                config.sns_client = double
         | 
| 20 | 
            +
                config.logger = Logger.new(nil)
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            class ExampleQueuePoller < Pheme::QueuePoller
         | 
| 2 | 
            +
              def handle(message)
         | 
| 3 | 
            +
                case message.status
         | 
| 4 | 
            +
                when 'complete', 'rejected'
         | 
| 5 | 
            +
                  ExampleMessageHandler.new(message: message).handle
         | 
| 6 | 
            +
                else
         | 
| 7 | 
            +
                  raise ArgumentError, "Unknown message status: #{message.status}"
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            describe Pheme::TopicPublisher do
         | 
| 2 | 
            +
              before(:each) { use_default_configuration! }
         | 
| 3 | 
            +
              subject { ExamplePublisher.new(topic_arn: "arn:aws:sns:whatever") }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              describe "#publish_events" do
         | 
| 6 | 
            +
                it "publishes the correct events" do
         | 
| 7 | 
            +
                  expect(Pheme.configuration.sns_client).to receive(:publish).with({
         | 
| 8 | 
            +
                    topic_arn: "arn:aws:sns:whatever",
         | 
| 9 | 
            +
                    message: {id: "id-0", status: "complete"}.to_json,
         | 
| 10 | 
            +
                  })
         | 
| 11 | 
            +
                  expect(Pheme.configuration.sns_client).to receive(:publish).with({
         | 
| 12 | 
            +
                    topic_arn: "arn:aws:sns:whatever",
         | 
| 13 | 
            +
                    message: {id: "id-1", status: "complete"}.to_json,
         | 
| 14 | 
            +
                  })
         | 
| 15 | 
            +
                  subject.publish_events
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,146 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: pheme
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Peter Graham
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2016-03-22 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: aws-sdk
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '2'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '2'
         | 
| 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'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '4'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: recursive-open-struct
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '1'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '1'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rspec
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '3.4'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '3.4'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rspec_junit_formatter
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0.2'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0.2'
         | 
| 83 | 
            +
            description: Ruby AWS SNS publisher + SQS poller & message handler
         | 
| 84 | 
            +
            email:
         | 
| 85 | 
            +
            - peter@wealthsimple.com
         | 
| 86 | 
            +
            executables: []
         | 
| 87 | 
            +
            extensions: []
         | 
| 88 | 
            +
            extra_rdoc_files: []
         | 
| 89 | 
            +
            files:
         | 
| 90 | 
            +
            - ".gitignore"
         | 
| 91 | 
            +
            - ".rspec"
         | 
| 92 | 
            +
            - Gemfile
         | 
| 93 | 
            +
            - LICENSE
         | 
| 94 | 
            +
            - README.md
         | 
| 95 | 
            +
            - circle.yml
         | 
| 96 | 
            +
            - lib/pheme.rb
         | 
| 97 | 
            +
            - lib/pheme/configuration.rb
         | 
| 98 | 
            +
            - lib/pheme/logger.rb
         | 
| 99 | 
            +
            - lib/pheme/message_handler.rb
         | 
| 100 | 
            +
            - lib/pheme/queue_poller.rb
         | 
| 101 | 
            +
            - lib/pheme/topic_publisher.rb
         | 
| 102 | 
            +
            - lib/pheme/version.rb
         | 
| 103 | 
            +
            - pheme.gemspec
         | 
| 104 | 
            +
            - spec/configuration_spec.rb
         | 
| 105 | 
            +
            - spec/message_handler_spec.rb
         | 
| 106 | 
            +
            - spec/queue_poller_spec.rb
         | 
| 107 | 
            +
            - spec/spec_helper.rb
         | 
| 108 | 
            +
            - spec/support/example_message_handler.rb
         | 
| 109 | 
            +
            - spec/support/example_publisher.rb
         | 
| 110 | 
            +
            - spec/support/example_queue_poller.rb
         | 
| 111 | 
            +
            - spec/topic_publisher_spec.rb
         | 
| 112 | 
            +
            - spec/version_spec.rb
         | 
| 113 | 
            +
            homepage: https://github.com/wealthsimple/pheme
         | 
| 114 | 
            +
            licenses:
         | 
| 115 | 
            +
            - MIT
         | 
| 116 | 
            +
            metadata: {}
         | 
| 117 | 
            +
            post_install_message: 
         | 
| 118 | 
            +
            rdoc_options: []
         | 
| 119 | 
            +
            require_paths:
         | 
| 120 | 
            +
            - lib
         | 
| 121 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 122 | 
            +
              requirements:
         | 
| 123 | 
            +
              - - ">="
         | 
| 124 | 
            +
                - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                  version: 2.0.0
         | 
| 126 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 127 | 
            +
              requirements:
         | 
| 128 | 
            +
              - - ">="
         | 
| 129 | 
            +
                - !ruby/object:Gem::Version
         | 
| 130 | 
            +
                  version: '0'
         | 
| 131 | 
            +
            requirements: []
         | 
| 132 | 
            +
            rubyforge_project: 
         | 
| 133 | 
            +
            rubygems_version: 2.5.1
         | 
| 134 | 
            +
            signing_key: 
         | 
| 135 | 
            +
            specification_version: 4
         | 
| 136 | 
            +
            summary: Ruby SNS publisher + SQS poller & message handler
         | 
| 137 | 
            +
            test_files:
         | 
| 138 | 
            +
            - spec/configuration_spec.rb
         | 
| 139 | 
            +
            - spec/message_handler_spec.rb
         | 
| 140 | 
            +
            - spec/queue_poller_spec.rb
         | 
| 141 | 
            +
            - spec/spec_helper.rb
         | 
| 142 | 
            +
            - spec/support/example_message_handler.rb
         | 
| 143 | 
            +
            - spec/support/example_publisher.rb
         | 
| 144 | 
            +
            - spec/support/example_queue_poller.rb
         | 
| 145 | 
            +
            - spec/topic_publisher_spec.rb
         | 
| 146 | 
            +
            - spec/version_spec.rb
         |