cangaroo 1.1.0 → 1.2.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/Rakefile +13 -11
  4. data/app/controllers/cangaroo/endpoint_controller.rb +24 -4
  5. data/app/interactors/cangaroo/count_json_object.rb +3 -0
  6. data/app/interactors/cangaroo/run_polls.rb +12 -0
  7. data/app/interactors/cangaroo/validate_json_schema.rb +9 -3
  8. data/app/jobs/cangaroo/job.rb +9 -27
  9. data/app/jobs/cangaroo/poll_job.rb +67 -0
  10. data/app/models/cangaroo/connection.rb +10 -2
  11. data/app/models/cangaroo/poll_timestamp.rb +17 -0
  12. data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +1 -1
  13. data/db/migrate/20160317020230_create_cangaroo_poll_timestamps.rb +12 -0
  14. data/lib/cangaroo.rb +2 -0
  15. data/lib/cangaroo/class_configuration.rb +24 -0
  16. data/lib/cangaroo/engine.rb +2 -0
  17. data/lib/cangaroo/logger.rb +66 -0
  18. data/lib/cangaroo/version.rb +1 -1
  19. data/lib/cangaroo/webhook/client.rb +21 -3
  20. data/lib/tasks/cangaroo_tasks.rake +7 -4
  21. data/spec/controllers/cangaroo/endpoint_controller_spec.rb +75 -35
  22. data/spec/fixtures/json_payload_connection_response.json +1 -0
  23. data/spec/fixtures/json_payload_empty.json +3 -0
  24. data/spec/interactors/cangaroo/perform_jobs_spec.rb +28 -13
  25. data/spec/interactors/cangaroo/run_polls_spec.rb +18 -0
  26. data/spec/interactors/cangaroo/validate_json_schema_spec.rb +8 -0
  27. data/spec/jobs/cangaroo/job_spec.rb +12 -1
  28. data/spec/jobs/cangaroo/poll_job_spec.rb +107 -0
  29. data/spec/lib/cangaroo/webhook/client_spec.rb +38 -0
  30. data/spec/rails_helper.rb +18 -44
  31. data/spec/support/database_cleaner.rb +14 -0
  32. data/spec/support/factory_girl.rb +5 -0
  33. data/spec/support/rails_app.rb +29 -0
  34. data/spec/support/shoulda_matchers.rb +8 -0
  35. data/spec/support/webmock.rb +8 -0
  36. metadata +71 -87
  37. data/spec/dummy/README.rdoc +0 -28
  38. data/spec/dummy/Rakefile +0 -6
  39. data/spec/dummy/app/assets/javascripts/application.js +0 -13
  40. data/spec/dummy/app/assets/stylesheets/application.css +0 -15
  41. data/spec/dummy/app/controllers/application_controller.rb +0 -5
  42. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  43. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  44. data/spec/dummy/bin/bundle +0 -3
  45. data/spec/dummy/bin/rails +0 -4
  46. data/spec/dummy/bin/rake +0 -4
  47. data/spec/dummy/bin/setup +0 -29
  48. data/spec/dummy/config.ru +0 -4
  49. data/spec/dummy/config/application.rb +0 -31
  50. data/spec/dummy/config/boot.rb +0 -5
  51. data/spec/dummy/config/database.yml +0 -11
  52. data/spec/dummy/config/environment.rb +0 -5
  53. data/spec/dummy/config/environments/development.rb +0 -41
  54. data/spec/dummy/config/environments/production.rb +0 -79
  55. data/spec/dummy/config/environments/test.rb +0 -42
  56. data/spec/dummy/config/initializers/assets.rb +0 -11
  57. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  58. data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
  59. data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  60. data/spec/dummy/config/initializers/inflections.rb +0 -16
  61. data/spec/dummy/config/initializers/mime_types.rb +0 -4
  62. data/spec/dummy/config/initializers/session_store.rb +0 -3
  63. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -9
  64. data/spec/dummy/config/locales/en.yml +0 -23
  65. data/spec/dummy/config/routes.rb +0 -4
  66. data/spec/dummy/config/secrets.yml +0 -22
  67. data/spec/dummy/db/development.sqlite3 +0 -0
  68. data/spec/dummy/db/schema.rb +0 -29
  69. data/spec/dummy/db/test.sqlite3 +0 -0
  70. data/spec/dummy/log/cangaroo.log +0 -0
  71. data/spec/dummy/log/development.log +0 -4024
  72. data/spec/dummy/log/test.log +0 -166964
  73. data/spec/dummy/public/404.html +0 -67
  74. data/spec/dummy/public/422.html +0 -67
  75. data/spec/dummy/public/500.html +0 -66
  76. data/spec/dummy/public/favicon.ico +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce036cf738d21b2abbc18a18f4f19fad3b7523d0
4
- data.tar.gz: d0ddb0558c19e926e8bbd23281517268a204a873
3
+ metadata.gz: 56de926de5b5e5d494a4a1f132e854a171191543
4
+ data.tar.gz: 94b41bce37f5b18794eabb1fa5a9cf623bb0087e
5
5
  SHA512:
6
- metadata.gz: b94e951ab0f344154d40d9cef544589e9e4db49c2049a706fec02ffeb614554c934f2d5407beb7fc99084fefb73db6093be14a4a5ef3445a3f96601b0cb3dda5
7
- data.tar.gz: 2893175b4d6680f957c061f22206ea2c83920bc67b158074388278236816f7c15a84b82a2922efda431f1cb174d418c270f89d93d99718dc375bfd7b4ca21fb7
6
+ metadata.gz: e927e1bf09605b62d8690aeed1c14599949fc5c3d7b5611626a23d67eb6006b0ba33a091e1dbbc23398f322119b0889b4e8fc170bb41b39977e011caa3e79d07
7
+ data.tar.gz: 2f24253cc3a8a1f39f07ed74624963e59034c11da0d75d492367ee2642469622d6c8993a3dc1f150eca0df429875b48958444d36ee786f96b0ff7e82c02ec40a
@@ -1,4 +1,4 @@
1
- Copyright 2015 Nebulab
1
+ Copyright 2016 Nebulab
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -5,6 +5,14 @@ rescue LoadError
5
5
  end
6
6
 
7
7
  require 'rdoc/task'
8
+ require 'bundler/gem_tasks'
9
+ require 'appraisal'
10
+ require 'rspec/core'
11
+ require 'rspec/core/rake_task'
12
+
13
+ Bundler::GemHelper.install_tasks
14
+
15
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
8
16
 
9
17
  RDoc::Task.new(:rdoc) do |rdoc|
10
18
  rdoc.rdoc_dir = 'rdoc'
@@ -14,19 +22,13 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
22
  rdoc.rdoc_files.include('lib/**/*.rb')
15
23
  end
16
24
 
17
- APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
18
- load 'rails/tasks/engine.rake'
19
-
20
25
  load 'rails/tasks/statistics.rake'
21
26
 
22
- Bundler::GemHelper.install_tasks
23
-
24
- Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
27
+ desc 'Run Cangaroo specs.'
28
+ RSpec::Core::RakeTask.new(:spec)
25
29
 
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')
30
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
31
+ task :default => :appraisal
32
+ end
31
33
 
32
34
  task default: :spec
@@ -18,8 +18,10 @@ module Cangaroo
18
18
 
19
19
  private
20
20
 
21
- def handle_error
22
- unless Rails.env.development?
21
+ def handle_error(exception)
22
+ if Rails.env.development?
23
+ raise(exception)
24
+ else
23
25
  render json: { error: 'Something went wrong!' }, status: 500
24
26
  end
25
27
  end
@@ -39,11 +41,29 @@ module Cangaroo
39
41
  end
40
42
 
41
43
  def key
42
- request.headers['X-Hub-Store']
44
+ if Rails.configuration.cangaroo.basic_auth
45
+ if !ActionController::HttpAuthentication::Basic.has_basic_credentials?(request)
46
+ return nil
47
+ end
48
+
49
+ user, pass = ActionController::HttpAuthentication::Basic::user_name_and_password(request)
50
+ user
51
+ else
52
+ request.headers['X-Hub-Store']
53
+ end
43
54
  end
44
55
 
45
56
  def token
46
- request.headers['X-Hub-Access-Token']
57
+ if Rails.configuration.cangaroo.basic_auth
58
+ if !ActionController::HttpAuthentication::Basic.has_basic_credentials?(request)
59
+ return nil
60
+ end
61
+
62
+ user, pass = ActionController::HttpAuthentication::Basic::user_name_and_password(request)
63
+ pass
64
+ else
65
+ request.headers['X-Hub-Access-Token']
66
+ end
47
67
  end
48
68
  end
49
69
  end
@@ -1,5 +1,6 @@
1
1
  module Cangaroo
2
2
  class CountJsonObject
3
+ include Cangaroo::Log
3
4
  include Interactor
4
5
 
5
6
  before :prepare_context
@@ -9,6 +10,8 @@ module Cangaroo
9
10
  o[k] = v.size
10
11
  o
11
12
  end
13
+
14
+ log.info 'total consumed payloads', count: context.object_count
12
15
  end
13
16
 
14
17
  private
@@ -0,0 +1,12 @@
1
+ module Cangaroo
2
+ class RunPolls
3
+ include Interactor
4
+
5
+ def call
6
+ context.jobs.each do |poll_job|
7
+ poll_job.new.enqueue
8
+ end
9
+ end
10
+
11
+ end
12
+ end
@@ -3,13 +3,13 @@ module Cangaroo
3
3
  include Interactor
4
4
 
5
5
  SCHEMA = {
6
+ 'id': 'Cangaroo Object',
6
7
  'type': 'object',
7
8
  'minProperties': 1,
8
9
  'additionalProperties': false,
9
10
  'patternProperties': {
10
11
  '^[a-z]*$': {
11
12
  'type': 'array',
12
- 'minItems': 1,
13
13
  'items': {
14
14
  'type': 'object',
15
15
  'required': ['id'],
@@ -26,8 +26,13 @@ module Cangaroo
26
26
  before :prepare_context
27
27
 
28
28
  def call
29
- JSON::Validator.fully_validate(SCHEMA, context.json_body).empty? ||
30
- context.fail!(message: 'wrong json schema', error_code: 500)
29
+ validation_response = JSON::Validator.fully_validate(SCHEMA, context.json_body)
30
+
31
+ if validation_response.empty?
32
+ return true
33
+ end
34
+
35
+ context.fail!(message: validation_response.join(', '), error_code: 500)
31
36
  end
32
37
 
33
38
  private
@@ -35,6 +40,7 @@ module Cangaroo
35
40
  def prepare_context
36
41
  context.request_id = context.json_body.delete('request_id')
37
42
  context.summary = context.json_body.delete('summary')
43
+ context.parameters = context.json_body.delete('parameters')
38
44
  end
39
45
  end
40
46
  end
@@ -1,21 +1,12 @@
1
1
  module Cangaroo
2
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
3
+ include Cangaroo::ClassConfiguration
10
4
 
11
- def path(path)
12
- self.webhook_path = path
13
- end
5
+ queue_as :cangaroo
14
6
 
15
- def parameters(parameters)
16
- self.webhook_parameters = parameters
17
- end
18
- end
7
+ class_configuration :connection
8
+ class_configuration :path, ''
9
+ class_configuration :parameters, {}
19
10
 
20
11
  def perform(*)
21
12
  restart_flow(connection_request)
@@ -37,6 +28,9 @@ module Cangaroo
37
28
  end
38
29
 
39
30
  def restart_flow(response)
31
+ # if no json was returned, the response should be discarded
32
+ return if response.blank?
33
+
40
34
  PerformFlow.call(
41
35
  source_connection: destination_connection,
42
36
  json_body: response.to_json,
@@ -57,19 +51,7 @@ module Cangaroo
57
51
  end
58
52
 
59
53
  def destination_connection
60
- @connection ||= Cangaroo::Connection.find_by!(name: connection_name)
61
- end
62
-
63
- def connection_name
64
- self.class.connection_name
65
- end
66
-
67
- def path
68
- self.class.webhook_path || ''
69
- end
70
-
71
- def parameters
72
- self.class.webhook_parameters || {}
54
+ @connection ||= Cangaroo::Connection.find_by!(name: connection)
73
55
  end
74
56
  end
75
57
  end
@@ -0,0 +1,67 @@
1
+ module Cangaroo
2
+ class PollJob < ActiveJob::Base
3
+ include Cangaroo::Log
4
+ include Cangaroo::ClassConfiguration
5
+
6
+ queue_as :cangaroo
7
+
8
+ class_configuration :connection
9
+ class_configuration :frequency, 1.day
10
+ class_configuration :path, ''
11
+ class_configuration :parameters, {}
12
+
13
+ def perform(*)
14
+ log.set_context(self)
15
+
16
+ last_poll = Time.at(arguments.first.fetch(:last_poll)).to_datetime
17
+ current_time = DateTime.now
18
+
19
+ if !perform?(current_time)
20
+ log.info 'skipping poll'
21
+ return
22
+ end
23
+
24
+ log.info 'initiating poll', last_poll: last_poll.to_i
25
+
26
+ response = Cangaroo::Webhook::Client.new(destination_connection, path)
27
+ .post({ last_poll: last_poll_timestamp.to_i }, @job_id, parameters)
28
+
29
+ log.info 'processing poll results'
30
+
31
+ command = HandleRequest.call(
32
+ key: destination_connection.key,
33
+ token: destination_connection.token,
34
+ json_body: response.to_json,
35
+ jobs: Rails.configuration.cangaroo.jobs
36
+ )
37
+
38
+ if !command.success?
39
+ fail Cangaroo::Webhook::Error, command.message
40
+ end
41
+
42
+ log.info 'updating last poll', last_poll: current_time
43
+
44
+ last_job_poll = Cangaroo::PollTimestamp.for_class(self.class)
45
+ last_job_poll.value = current_time
46
+ last_job_poll.save!
47
+
48
+ log.reset_context!
49
+ end
50
+
51
+ def perform?(execution_time)
52
+ last_poll_timestamp.nil? ||
53
+ execution_time.to_i - last_poll_timestamp.to_i > self.class.frequency
54
+ end
55
+
56
+ protected
57
+
58
+ def last_poll_timestamp
59
+ @last_poll_timestamp ||= Cangaroo::PollTimestamp.for_class(self.class).value
60
+ end
61
+
62
+ def destination_connection
63
+ @connection ||= Cangaroo::Connection.find_by!(name: connection)
64
+ end
65
+
66
+ end
67
+ end
@@ -2,11 +2,19 @@ module Cangaroo
2
2
  class Connection < ActiveRecord::Base
3
3
  serialize :parameters
4
4
 
5
- validates :name, :url, :key, :token, presence: true
6
- validates :name, :url, :key, :token, uniqueness: true
5
+ validates :name, :url, :token, presence: true, uniqueness: true
6
+ validates :key, presence: true, uniqueness: true, if: -> { !Rails.configuration.cangaroo.basic_auth }
7
+
8
+ after_initialize :set_default_parameters
7
9
 
8
10
  def self.authenticate(key, token)
9
11
  where(key: key, token: token).first
10
12
  end
13
+
14
+ private
15
+
16
+ def set_default_parameters
17
+ self.parameters ||= {}
18
+ end
11
19
  end
12
20
  end
@@ -0,0 +1,17 @@
1
+ module Cangaroo
2
+ class PollTimestamp < ActiveRecord::Base
3
+ serialize :value
4
+
5
+ belongs_to :connection
6
+
7
+ validates_uniqueness_of :job, scope: :connection
8
+
9
+ def self.for_class(klass)
10
+ self.where(
11
+ job: klass.to_s,
12
+ connection: Cangaroo::Connection.find_by!(name: klass.connection)
13
+ ).first_or_initialize
14
+ end
15
+
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  class AddParametersToCangarooConnection < ActiveRecord::Migration
2
2
  def change
3
- add_column :cangaroo_connections, :parameters, :text, default: {}
3
+ add_column :cangaroo_connections, :parameters, :text
4
4
  end
5
5
  end
@@ -0,0 +1,12 @@
1
+ class CreateCangarooPollTimestamps < ActiveRecord::Migration
2
+ def change
3
+ create_table :cangaroo_poll_timestamps do |t|
4
+ t.string :job
5
+ t.references :connection
6
+ t.text :value
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :cangaroo_poll_timestamps, [:job], :name => 'index_cangaroo_poll_timestamps_on_job'
11
+ end
12
+ end
@@ -3,8 +3,10 @@ require 'interactor'
3
3
  require 'json-schema'
4
4
  require 'httparty'
5
5
 
6
+ require 'cangaroo/logger'
6
7
  require 'cangaroo/webhook/error'
7
8
  require 'cangaroo/webhook/client'
9
+ require 'cangaroo/class_configuration'
8
10
 
9
11
  module Cangaroo
10
12
  end
@@ -0,0 +1,24 @@
1
+ module Cangaroo
2
+ module ClassConfiguration
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def class_configuration(key, default = nil)
7
+ class_attribute :"_#{key}"
8
+
9
+ define_singleton_method(key) do |*args|
10
+ if args.empty?
11
+ return self.send(:"_#{key}") || default
12
+ end
13
+
14
+ self.send(:"_#{key}=", args.first)
15
+ end
16
+
17
+ define_method(key) do
18
+ self.send(:"_#{key}") || default
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -12,6 +12,8 @@ module Cangaroo
12
12
  config.before_configuration do
13
13
  Rails.configuration.cangaroo = ActiveSupport::OrderedOptions.new
14
14
  Rails.configuration.cangaroo.jobs = []
15
+ Rails.configuration.cangaroo.poll_jobs = []
16
+ Rails.configuration.cangaroo.basic_auth = false
15
17
  end
16
18
  end
17
19
  end
@@ -0,0 +1,66 @@
1
+ require 'logger'
2
+
3
+ module Cangaroo
4
+ module Log
5
+
6
+ def log
7
+ Cangaroo::Log::Writer.instance
8
+ end
9
+
10
+ class Writer
11
+ include Singleton
12
+
13
+ attr_reader :default_tags
14
+
15
+ def initialize
16
+ @l = Logger.new(STDOUT)
17
+ @default_tags = {}
18
+ end
19
+
20
+ def reset_context!
21
+ @default_tags = {}
22
+ end
23
+
24
+ def set_context(job)
25
+ reset_context!
26
+
27
+ @default_tags.merge!({
28
+ job: job.class.to_s,
29
+ job_id: job.job_id,
30
+ connection: job.class.connection
31
+ })
32
+ end
33
+
34
+ def error(msg, opts={})
35
+ @l.error("#{msg}: #{stringify_tags(opts)}")
36
+ end
37
+
38
+ def info(msg, opts={})
39
+ @l.info("#{msg}: #{stringify_tags(opts)}")
40
+ end
41
+
42
+ def debug(msg, opts={})
43
+ @l.debug("#{msg}: #{stringify_tags(opts)}")
44
+ end
45
+
46
+ def warn(msg, opts={})
47
+ @l.warn("#{msg}: #{stringify_tags(opts)}")
48
+ end
49
+
50
+ private
51
+
52
+ def stringify_tags(additional_tags)
53
+ additional_tags = additional_tags.dup
54
+
55
+ if translation = additional_tags.delete(:translation)
56
+ additional_tags[:translation_id] = translation.id
57
+ # TODO extract other important info from the translation
58
+ end
59
+
60
+ @default_tags.merge(additional_tags).map { |k,v| "#{k}=#{v}" }.join(' ')
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end