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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +32 -0
- data/app/controllers/cangaroo/application_controller.rb +4 -0
- data/app/controllers/cangaroo/endpoint_controller.rb +49 -0
- data/app/interactors/cangaroo/authenticate_connection.rb +19 -0
- data/app/interactors/cangaroo/count_json_object.rb +20 -0
- data/app/interactors/cangaroo/handle_request.rb +10 -0
- data/app/interactors/cangaroo/perform_jobs.rb +31 -0
- data/app/interactors/cangaroo/validate_json_schema.rb +31 -0
- data/app/jobs/cangaroo/job.rb +63 -0
- data/app/models/cangaroo/connection.rb +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20151028172151_create_cangaroo_connections.rb +12 -0
- data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +5 -0
- data/lib/cangaroo.rb +10 -0
- data/lib/cangaroo/engine.rb +17 -0
- data/lib/cangaroo/version.rb +3 -0
- data/lib/cangaroo/webhook/client.rb +44 -0
- data/lib/cangaroo/webhook/error.rb +6 -0
- data/lib/tasks/cangaroo_tasks.rake +4 -0
- data/spec/controllers/cangaroo/endpoint_controller_spec.rb +72 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +31 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +11 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +29 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/cangaroo.log +0 -0
- data/spec/dummy/log/development.log +3466 -0
- data/spec/dummy/log/test.log +139640 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/cangaroo_connections.rb +9 -0
- data/spec/fixtures/json_payload_no_id.json +21 -0
- data/spec/fixtures/json_payload_ok.json +22 -0
- data/spec/fixtures/json_payload_wrong_key.json +22 -0
- data/spec/interactors/cangaroo/authenticate_connection_spec.rb +46 -0
- data/spec/interactors/cangaroo/count_json_object_spec.rb +17 -0
- data/spec/interactors/cangaroo/handle_request_spec.rb +18 -0
- data/spec/interactors/cangaroo/perform_jobs_spec.rb +42 -0
- data/spec/interactors/cangaroo/validate_json_schema_spec.rb +51 -0
- data/spec/jobs/cangaroo/job_spec.rb +55 -0
- data/spec/lib/cangaroo/webhook/client_spec.rb +69 -0
- data/spec/models/cangaroo/connection_spec.rb +15 -0
- data/spec/rails_helper.rb +85 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/spec_helpers.rb +11 -0
- metadata +345 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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,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,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
|
data/config/routes.rb
ADDED
data/lib/cangaroo.rb
ADDED
@@ -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,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,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
|