cangaroo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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