cangaroo 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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +32 -0
  4. data/app/controllers/cangaroo/application_controller.rb +4 -0
  5. data/app/controllers/cangaroo/endpoint_controller.rb +49 -0
  6. data/app/interactors/cangaroo/authenticate_connection.rb +19 -0
  7. data/app/interactors/cangaroo/count_json_object.rb +20 -0
  8. data/app/interactors/cangaroo/handle_request.rb +10 -0
  9. data/app/interactors/cangaroo/perform_jobs.rb +31 -0
  10. data/app/interactors/cangaroo/validate_json_schema.rb +31 -0
  11. data/app/jobs/cangaroo/job.rb +63 -0
  12. data/app/models/cangaroo/connection.rb +12 -0
  13. data/config/routes.rb +3 -0
  14. data/db/migrate/20151028172151_create_cangaroo_connections.rb +12 -0
  15. data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +5 -0
  16. data/lib/cangaroo.rb +10 -0
  17. data/lib/cangaroo/engine.rb +17 -0
  18. data/lib/cangaroo/version.rb +3 -0
  19. data/lib/cangaroo/webhook/client.rb +44 -0
  20. data/lib/cangaroo/webhook/error.rb +6 -0
  21. data/lib/tasks/cangaroo_tasks.rake +4 -0
  22. data/spec/controllers/cangaroo/endpoint_controller_spec.rb +72 -0
  23. data/spec/dummy/README.rdoc +28 -0
  24. data/spec/dummy/Rakefile +6 -0
  25. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  26. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  27. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  28. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/bin/bundle +3 -0
  31. data/spec/dummy/bin/rails +4 -0
  32. data/spec/dummy/bin/rake +4 -0
  33. data/spec/dummy/bin/setup +29 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +31 -0
  36. data/spec/dummy/config/boot.rb +5 -0
  37. data/spec/dummy/config/database.yml +11 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +41 -0
  40. data/spec/dummy/config/environments/production.rb +79 -0
  41. data/spec/dummy/config/environments/test.rb +42 -0
  42. data/spec/dummy/config/initializers/assets.rb +11 -0
  43. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  45. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  46. data/spec/dummy/config/initializers/inflections.rb +16 -0
  47. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  48. data/spec/dummy/config/initializers/session_store.rb +3 -0
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  50. data/spec/dummy/config/locales/en.yml +23 -0
  51. data/spec/dummy/config/routes.rb +4 -0
  52. data/spec/dummy/config/secrets.yml +22 -0
  53. data/spec/dummy/db/development.sqlite3 +0 -0
  54. data/spec/dummy/db/schema.rb +29 -0
  55. data/spec/dummy/db/test.sqlite3 +0 -0
  56. data/spec/dummy/log/cangaroo.log +0 -0
  57. data/spec/dummy/log/development.log +3466 -0
  58. data/spec/dummy/log/test.log +139640 -0
  59. data/spec/dummy/public/404.html +67 -0
  60. data/spec/dummy/public/422.html +67 -0
  61. data/spec/dummy/public/500.html +66 -0
  62. data/spec/dummy/public/favicon.ico +0 -0
  63. data/spec/factories/cangaroo_connections.rb +9 -0
  64. data/spec/fixtures/json_payload_no_id.json +21 -0
  65. data/spec/fixtures/json_payload_ok.json +22 -0
  66. data/spec/fixtures/json_payload_wrong_key.json +22 -0
  67. data/spec/interactors/cangaroo/authenticate_connection_spec.rb +46 -0
  68. data/spec/interactors/cangaroo/count_json_object_spec.rb +17 -0
  69. data/spec/interactors/cangaroo/handle_request_spec.rb +18 -0
  70. data/spec/interactors/cangaroo/perform_jobs_spec.rb +42 -0
  71. data/spec/interactors/cangaroo/validate_json_schema_spec.rb +51 -0
  72. data/spec/jobs/cangaroo/job_spec.rb +55 -0
  73. data/spec/lib/cangaroo/webhook/client_spec.rb +69 -0
  74. data/spec/models/cangaroo/connection_spec.rb +15 -0
  75. data/spec/rails_helper.rb +85 -0
  76. data/spec/spec_helper.rb +9 -0
  77. data/spec/support/spec_helpers.rb +11 -0
  78. metadata +345 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2d7be05c09c24fab1c3762ff2987c408f29171af
4
+ data.tar.gz: 627378f34edd49f3dd92283f736b2b07635057cd
5
+ SHA512:
6
+ metadata.gz: 12591483c6345eb3b72f1d61ebd8f309a7241d10f50699a441536b762276168987171a43735e6e12e7181c230b28770f4848b88f72e308b5e8c293c8f0cbe43e
7
+ data.tar.gz: c46a47b09ca10ca5d4cb562c314c7a93e300ff86f5a6d95061074165b3cbcb64ac459d3eaeb89d525fc030a3e3708b2fb752d168aa494631166ac9eb57d51e1b
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Nebulab
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Cangaroo'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
25
+
26
+ require 'rspec/core'
27
+ require 'rspec/core/rake_task'
28
+
29
+ desc 'Run all specs in spec directory (excluding plugin specs)'
30
+ RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
31
+
32
+ task default: :spec
@@ -0,0 +1,4 @@
1
+ module Cangaroo
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,49 @@
1
+ require_dependency 'cangaroo/application_controller'
2
+
3
+ module Cangaroo
4
+ class EndpointController < ApplicationController
5
+ before_action :ensure_json_request
6
+ before_action :handle_request
7
+
8
+ rescue_from Exception, with: :handle_error
9
+
10
+ def create
11
+ if @command.success?
12
+ render json: @command.object_count, status: 202
13
+ else
14
+ render json: { error: @command.message },
15
+ status: @command.error_code
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def handle_error
22
+ unless Rails.env.development?
23
+ render json: { error: 'Something went wrong!' }, status: 500
24
+ end
25
+ end
26
+
27
+ def handle_request
28
+ @command = HandleRequest.call(
29
+ key: key,
30
+ token: token,
31
+ json_body: params[:endpoint].to_json,
32
+ jobs: Rails.configuration.cangaroo.jobs
33
+ )
34
+ end
35
+
36
+ def ensure_json_request
37
+ return if request.headers['Content-Type'] == 'application/json'
38
+ render nothing: true, status: 406
39
+ end
40
+
41
+ def key
42
+ request.headers['X-Hub-Store']
43
+ end
44
+
45
+ def token
46
+ request.headers['X-Hub-Access-Token']
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ module Cangaroo
2
+ class AuthenticateConnection
3
+ include Interactor
4
+
5
+ before :prepare_context
6
+
7
+ def call
8
+ context.source_connection ||
9
+ context.fail!(message: 'wrong credentials', error_code: 401)
10
+ end
11
+
12
+ private
13
+
14
+ def prepare_context
15
+ context.source_connection =
16
+ Cangaroo::Connection.authenticate(context.key, context.token)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Cangaroo
2
+ class CountJsonObject
3
+ include Interactor
4
+
5
+ before :prepare_context
6
+
7
+ def call
8
+ context.object_count = context.data.inject({}) do |o, (k, v)|
9
+ o[k] = v.size
10
+ o
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def prepare_context
17
+ context.data = JSON.parse(context.json_body)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module Cangaroo
2
+ class HandleRequest
3
+ include Interactor::Organizer
4
+
5
+ organize Cangaroo::AuthenticateConnection,
6
+ Cangaroo::ValidateJsonSchema,
7
+ Cangaroo::CountJsonObject,
8
+ Cangaroo::PerformJobs
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ module Cangaroo
2
+ class PerformJobs
3
+ include Interactor
4
+
5
+ def call
6
+ data.each do |type, payloads|
7
+ payloads.each { |payload| enqueue_jobs(type, payload) }
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def data
14
+ @data ||= JSON.parse(context.json_body)
15
+ end
16
+
17
+ def enqueue_jobs(type, payload)
18
+ initialize_jobs(type, payload).select(&:perform?).each(&:enqueue)
19
+ end
20
+
21
+ def initialize_jobs(type, payload)
22
+ context.jobs.map do |klass|
23
+ klass.new(
24
+ connection: context.source_connection,
25
+ type: type,
26
+ payload: payload
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Cangaroo
2
+ class ValidateJsonSchema
3
+ include Interactor
4
+
5
+ SCHEMA = {
6
+ 'type': 'object',
7
+ 'minProperties': 1,
8
+ 'additionalProperties': false,
9
+ 'patternProperties': {
10
+ '^[a-z]*$': {
11
+ 'type': 'array',
12
+ 'minItems': 1,
13
+ 'items': {
14
+ 'type': 'object',
15
+ 'required': ['id'],
16
+ 'properties': {
17
+ 'id': {
18
+ 'type': 'string'
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }.freeze
25
+
26
+ def call
27
+ JSON::Validator.fully_validate(SCHEMA, context.json_body).empty? ||
28
+ context.fail!(message: 'wrong json schema', error_code: 500)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,63 @@
1
+ module Cangaroo
2
+ class Job < ActiveJob::Base
3
+ queue_as :cangaroo
4
+
5
+ class_attribute :connection_name, :webhook_path, :webhook_parameters
6
+ class << self
7
+ def connection(name)
8
+ self.connection_name = name
9
+ end
10
+
11
+ def path(path)
12
+ self.webhook_path = path
13
+ end
14
+
15
+ def parameters(parameters)
16
+ self.webhook_parameters = parameters
17
+ end
18
+ end
19
+
20
+ def perform(*)
21
+ Cangaroo::Webhook::Client.new(destination_connection, path)
22
+ .post(transform, @job_id, parameters)
23
+ end
24
+
25
+ def perform?
26
+ fail NotImplementedError
27
+ end
28
+
29
+ def transform
30
+ { type.singularize => payload }
31
+ end
32
+
33
+ private
34
+
35
+ def source_connection
36
+ arguments.first.fetch(:connection)
37
+ end
38
+
39
+ def type
40
+ arguments.first.fetch(:type)
41
+ end
42
+
43
+ def payload
44
+ arguments.first.fetch(:payload)
45
+ end
46
+
47
+ def destination_connection
48
+ @connection ||= Cangaroo::Connection.find_by!(name: connection_name)
49
+ end
50
+
51
+ def connection_name
52
+ self.class.connection_name
53
+ end
54
+
55
+ def path
56
+ self.class.webhook_path || ''
57
+ end
58
+
59
+ def parameters
60
+ self.class.webhook_parameters || {}
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,12 @@
1
+ module Cangaroo
2
+ class Connection < ActiveRecord::Base
3
+ serialize :parameters
4
+
5
+ validates :name, :url, :key, :token, presence: true
6
+ validates :name, :url, :key, :token, uniqueness: true
7
+
8
+ def self.authenticate(key, token)
9
+ where(key: key, token: token).first
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ Cangaroo::Engine.routes.draw do
2
+ resources :endpoint, only: [:create], constraints: { format: :json }
3
+ end
@@ -0,0 +1,12 @@
1
+ class CreateCangarooConnections < ActiveRecord::Migration
2
+ def change
3
+ create_table :cangaroo_connections do |t|
4
+ t.string :name
5
+ t.string :url
6
+ t.string :key
7
+ t.string :token
8
+
9
+ t.timestamps null: false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ class AddParametersToCangarooConnection < ActiveRecord::Migration
2
+ def change
3
+ add_column :cangaroo_connections, :parameters, :text, default: {}
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'cangaroo/engine'
2
+ require 'interactor'
3
+ require 'json-schema'
4
+ require 'httparty'
5
+
6
+ require 'cangaroo/webhook/error'
7
+ require 'cangaroo/webhook/client'
8
+
9
+ module Cangaroo
10
+ end
@@ -0,0 +1,17 @@
1
+ module Cangaroo
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Cangaroo
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec, fixture: false
7
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
8
+ g.assets false
9
+ g.helper false
10
+ end
11
+
12
+ config.before_configuration do
13
+ Rails.configuration.cangaroo = ActiveSupport::OrderedOptions.new
14
+ Rails.configuration.cangaroo.jobs = []
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Cangaroo
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,44 @@
1
+ module Cangaroo
2
+ module Webhook
3
+ class Client
4
+ include HTTParty
5
+
6
+ format :json
7
+
8
+ attr_accessor :connection, :path
9
+
10
+ def initialize(connection, path)
11
+ @connection = connection
12
+ @path = path
13
+ end
14
+
15
+ def post(payload, request_id, parameters)
16
+ request_body = body(payload, request_id, parameters).to_json
17
+ req = self.class.post(url, headers: headers, body: request_body)
18
+ if req.response.code == '200'
19
+ req.parsed_response
20
+ else
21
+ fail Cangaroo::Webhook::Error, req.parsed_response['summary']
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def url
28
+ URI.parse(
29
+ HTTParty.normalize_base_uri(connection.url)
30
+ ).merge(path.to_s).to_s
31
+ end
32
+
33
+ def headers
34
+ { 'X_HUB_TOKEN' => connection.token }
35
+ end
36
+
37
+ def body(payload, request_id, parameters)
38
+ { request_id: request_id,
39
+ parameters: connection.parameters.deep_merge(parameters)
40
+ }.merge(payload)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ module Cangaroo
2
+ module Webhook
3
+ class Error < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cangaroo do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,72 @@
1
+ require 'rails_helper'
2
+
3
+ module Cangaroo
4
+ RSpec.describe EndpointController, type: :controller do
5
+ routes { Cangaroo::Engine.routes }
6
+ let(:connection) { create :cangaroo_connection }
7
+ let(:request_payload) { JSON.parse(load_fixture('json_payload_ok.json')) }
8
+
9
+ before do
10
+ request.headers['Content-Type'] = 'application/json'
11
+ request.headers['X-Hub-Store'] = connection.key
12
+ request.headers['X-Hub-Access-Token'] = connection.token
13
+ end
14
+
15
+ describe '#create' do
16
+ before do
17
+ post :create, request_payload
18
+ end
19
+
20
+ it 'accepts only application/json requests' do
21
+ expect(response.status).to eq(202)
22
+
23
+ request.headers['Content-Type'] = 'application/text'
24
+ post :create
25
+ expect(response.status).to eq(406)
26
+ end
27
+
28
+ context 'when success' do
29
+ let(:auth_headers) {}
30
+
31
+ it 'responds with 200' do
32
+ expect(response.status).to eq(202)
33
+ end
34
+
35
+ it 'responds with the number of objects received in payload' do
36
+ res = JSON.parse(response.body)
37
+ expect(res).to eq('orders' => 2, 'shipments' => 2)
38
+ end
39
+ end
40
+
41
+ context 'when error' do
42
+ before do
43
+ request.headers['X-Hub-Access-Token'] = 'wrongtoken'
44
+ post :create, request_payload
45
+ end
46
+
47
+ it 'responds with the command error code' do
48
+ expect(response.status).to eq(401)
49
+ end
50
+
51
+ it 'responds with error messages in the body' do
52
+ expect(JSON.parse(response.body)['error']).to be_present
53
+ end
54
+ end
55
+
56
+ context 'when an exception was raised' do
57
+ before do
58
+ HandleRequest.stub(:call).and_raise('An error')
59
+ post :create, request_payload
60
+ end
61
+
62
+ it 'responds with 500' do
63
+ expect(response.status).to eq(500)
64
+ end
65
+
66
+ it 'responds with error messages in the body' do
67
+ expect(JSON.parse(response.body)['error']).to eq 'Something went wrong!'
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end