cangaroo 1.2.0 → 1.3.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/controllers/cangaroo/endpoint_controller.rb +22 -28
  4. data/app/interactors/cangaroo/count_json_object.rb +2 -12
  5. data/app/interactors/cangaroo/perform_jobs.rb +2 -6
  6. data/app/interactors/cangaroo/run_polls.rb +0 -1
  7. data/app/interactors/cangaroo/validate_json_schema.rb +3 -8
  8. data/app/jobs/cangaroo/base_job.rb +35 -0
  9. data/app/jobs/cangaroo/job.rb +2 -36
  10. data/app/jobs/cangaroo/poll_job.rb +20 -44
  11. data/app/jobs/cangaroo/push_job.rb +15 -0
  12. data/app/models/cangaroo/connection.rb +3 -3
  13. data/app/models/cangaroo/poll_timestamp.rb +1 -2
  14. data/db/migrate/20151028172151_create_cangaroo_connections.rb +1 -1
  15. data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +1 -1
  16. data/db/migrate/20160317020230_create_cangaroo_poll_timestamps.rb +2 -2
  17. data/lib/cangaroo.rb +9 -0
  18. data/lib/cangaroo/class_configuration.rb +7 -4
  19. data/lib/cangaroo/engine.rb +2 -0
  20. data/lib/cangaroo/logger.rb +13 -58
  21. data/lib/cangaroo/logger_helper.rb +13 -0
  22. data/lib/cangaroo/migration.rb +11 -0
  23. data/lib/cangaroo/version.rb +1 -1
  24. data/lib/cangaroo/webhook/client.rb +27 -15
  25. data/spec/factories/cangaroo_connections.rb +26 -2
  26. data/spec/features/push_flow_spec.rb +79 -0
  27. data/spec/fixtures/json_payload_ok.json +3 -1
  28. data/spec/interactors/cangaroo/count_json_object_spec.rb +4 -2
  29. data/spec/interactors/cangaroo/perform_jobs_spec.rb +2 -3
  30. data/spec/jobs/cangaroo/poll_job_spec.rb +15 -17
  31. data/spec/jobs/cangaroo/{job_spec.rb → push_job_spec.rb} +25 -17
  32. data/spec/lib/cangaroo/logger_helper_spec.rb +21 -0
  33. data/spec/lib/cangaroo/logger_spec.rb +83 -0
  34. data/spec/lib/cangaroo/webhook/client_spec.rb +39 -24
  35. data/spec/rails_helper.rb +4 -2
  36. data/spec/{controllers/cangaroo/endpoint_controller_spec.rb → requests/cangaroo/endpoint_spec.rb} +21 -30
  37. data/spec/support/jobs/confirm_order_mail_job.rb +10 -0
  38. data/spec/support/jobs/erp_job.rb +10 -0
  39. data/spec/support/jobs/fake_push_job.rb +5 -0
  40. data/spec/support/jobs/shipped_order_mail_job.rb +10 -0
  41. data/spec/support/jobs/store_job.rb +10 -0
  42. data/spec/support/jobs/warehouse_job.rb +10 -0
  43. data/spec/support/rails_app.rb +1 -0
  44. data/spec/support/spec_helpers.rb +23 -0
  45. metadata +53 -32
  46. data/app/controllers/cangaroo/application_controller.rb +0 -4
@@ -1,21 +1,15 @@
1
1
  require 'rails_helper'
2
2
 
3
3
  module Cangaroo
4
- RSpec.describe Job, type: :job do
5
- class FakeJob < Cangaroo::Job
6
- connection :store
7
- path '/webhook_path'
8
- parameters(email: 'info@nebulab.it')
9
- end
10
-
11
- let(:job_class) { FakeJob }
4
+ RSpec.describe PushJob, type: :job do
5
+ let(:job_class) { FakePushJob }
12
6
  let(:destination_connection) { create(:cangaroo_connection) }
13
7
  let(:type) { 'orders' }
14
8
  let(:payload) { { id: 'O123' } }
15
9
  let(:connection_response) { parse_fixture('json_payload_connection_response.json') }
16
10
 
17
11
  let(:options) do
18
- { connection: destination_connection,
12
+ { source_connection: destination_connection,
19
13
  type: type,
20
14
  payload: payload }
21
15
  end
@@ -24,15 +18,17 @@ module Cangaroo
24
18
  Cangaroo::Webhook::Client.new(destination_connection, '/webhook_path')
25
19
  end
26
20
 
21
+ let(:fake_command) { double('fake perform flow command', success?: true) }
22
+
23
+ let(:job) { job_class.new(options) }
24
+
27
25
  before do
28
26
  allow(client).to receive(:post).and_return(connection_response)
29
27
  allow(Cangaroo::Webhook::Client).to receive(:new).and_return(client)
30
- allow(Cangaroo::PerformFlow).to receive(:call)
28
+ allow(Cangaroo::PerformFlow).to receive(:call).and_return(fake_command)
31
29
  end
32
30
 
33
31
  describe '#perform' do
34
- let(:job) { job_class.new(options) }
35
-
36
32
  it 'instantiates a Cangaroo::Webhook::Client' do
37
33
  expect(Cangaroo::Webhook::Client).to receive(:new)
38
34
  .with(destination_connection, '/webhook_path')
@@ -41,25 +37,34 @@ module Cangaroo
41
37
  end
42
38
 
43
39
  it 'calls post on client' do
44
- job.perform
40
+ job.perform_now
45
41
  expect(client).to have_received(:post)
46
42
  .with(job.transform, job.job_id, email: 'info@nebulab.it')
47
43
  end
48
44
 
49
45
  it 'restart the flow' do
50
- job.perform
46
+ job.class.process_response(true)
47
+ job.perform_now
51
48
  expect(Cangaroo::PerformFlow).to have_received(:call)
52
49
  .once
53
50
  .with(source_connection: destination_connection,
54
- json_body: connection_response.to_json,
51
+ json_body: connection_response,
55
52
  jobs: Rails.configuration.cangaroo.jobs)
56
53
  end
57
54
 
55
+ it 'should not restart the flow when disabled' do
56
+ job.class.process_response(false)
57
+
58
+ job.perform_now
59
+
60
+ expect(Cangaroo::PerformFlow).to_not have_received(:call)
61
+ end
62
+
58
63
  context 'endpoint provides a empty response' do
59
64
  it 'should not restart the flow' do
60
65
  allow(client).to receive(:post).and_return('')
61
66
 
62
- job.perform
67
+ job.perform_now
63
68
 
64
69
  expect(Cangaroo::PerformFlow).to_not have_received(:call)
65
70
  end
@@ -71,7 +76,10 @@ module Cangaroo
71
76
  end
72
77
 
73
78
  describe '#transform' do
74
- it { expect(job_class.new(options).transform).to eq('order' => payload) }
79
+ it 'return a single element hash with singularized type as key and payload as value' do
80
+ job.perform_now
81
+ expect(job.transform).to eq('order' => payload)
82
+ end
75
83
  end
76
84
  end
77
85
  end
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ describe Cangaroo::LoggerHelper do
4
+ describe '#job_tags' do
5
+ let(:job_class) { FakePushJob }
6
+ let(:type) { 'orders' }
7
+ let(:payload) { { id: 'O123' } }
8
+ let(:options) do
9
+ { connection: destination_connection,
10
+ type: type,
11
+ payload: payload }
12
+ end
13
+ let(:job) { job_class.new(options) }
14
+ let!(:destination_connection) { create(:cangaroo_connection) }
15
+ let(:extra_tags) { { tag1: 1, tag2: 2 } }
16
+
17
+ it 'merges given tags with job class name, job_id and connection name' do
18
+ expect(job.job_tags(extra_tags)).to eq(extra_tags.merge(job: 'FakePushJob', job_id: job.job_id, connection: destination_connection.name))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,83 @@
1
+ require 'rails_helper'
2
+
3
+ class CustomLogger
4
+ def log(_message); end
5
+
6
+ def unknown(_message, _opts = {}); end
7
+
8
+ def debug(_message, _opts = {}); end
9
+
10
+ def info(_message, _opts = {}); end
11
+
12
+ def warn(_message, _opts = {}); end
13
+
14
+ def error(_message, _opts = {}); end
15
+ end
16
+
17
+ describe Cangaroo::Logger do
18
+ let(:cangaroo_logger) { Cangaroo::Logger.send(:new) }
19
+ before { Rails.configuration.cangaroo.logger = logger }
20
+
21
+ describe '#logger' do
22
+ subject { cangaroo_logger.logger }
23
+
24
+ context 'when Rails.configuration.cangaroo.logger is set' do
25
+ let(:logger) { CustomLogger.new }
26
+
27
+ it { is_expected.to eq(logger) }
28
+ end
29
+
30
+ context 'when Rails.configuration.cangaroo.logger is not set' do
31
+ let(:logger) { nil }
32
+
33
+ it { is_expected.to eq(Rails.logger) }
34
+ end
35
+ end
36
+
37
+ context 'log methods' do
38
+ let(:message) { 'message' }
39
+ let(:opts) { { opt1: 1, opt2: 2 } }
40
+
41
+ describe '#log' do
42
+ after { cangaroo_logger.log(message, opts) }
43
+
44
+ context 'when logger can receive given parameters' do
45
+ let(:logger) { nil }
46
+
47
+ it 'calls the logger log method with the given params' do
48
+ expect(Rails.logger).to receive(:log).with(message, opts)
49
+ end
50
+ end
51
+
52
+ context 'when logger can not receive given parameters' do
53
+ let(:logger) { CustomLogger.new }
54
+
55
+ it 'calls the logger log method with an array of message and params' do
56
+ expect(logger).to receive(:log).with([message, opts])
57
+ end
58
+ end
59
+ end
60
+
61
+ %i(unknown debug info warn error).each do |log_method|
62
+ describe "##{log_method}" do
63
+ after { cangaroo_logger.send(log_method, message, opts) }
64
+
65
+ context 'when logger can receive given parameters' do
66
+ let(:logger) { CustomLogger.new }
67
+
68
+ it "calls the logger #{log_method} method with the given params" do
69
+ expect(logger).to receive(log_method).with(message, opts)
70
+ end
71
+ end
72
+
73
+ context 'when logger can not receive given parameters' do
74
+ let(:logger) { nil }
75
+
76
+ it "calls the logger #{log_method} method with an array of message and params" do
77
+ expect(Rails.logger).to receive(log_method).with([message, opts])
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -11,11 +11,12 @@ module Cangaroo
11
11
 
12
12
  let(:request_id) { '123456' }
13
13
  let(:parameters) { { email: 'info@nebulab.it' } }
14
- let(:payload) { { order: { id: 'R12345', state: 'completed' } } }
14
+ let(:payload) do
15
+ { order: { id: 'R12345', state: 'completed' } }
16
+ end
15
17
 
16
18
  let(:response) do
17
- { 'request_id': '52f367367575e449c3000001',
18
- 'summary': 'Successfully updated order for R12345' }
19
+ { 'request_id' => '52f367367575e449c3000001', 'summary' => 'Successfully updated order for R12345' }
19
20
  end
20
21
 
21
22
  before do
@@ -28,13 +29,12 @@ module Cangaroo
28
29
  client.post(payload, request_id, parameters)
29
30
  expect(WebMock).to have_requested(:post,
30
31
  'http://www.store.com/api_path')
31
- .with(
32
- headers: { 'X_HUB_TOKEN': connection.token },
33
- body: {
34
- request_id: request_id,
35
- parameters: connection.parameters.deep_merge(parameters),
36
- order: { id: 'R12345', state: 'completed' }
37
- }.to_json)
32
+ .with(headers: { 'X_HUB_TOKEN' => connection.token },
33
+ body: {
34
+ request_id: request_id,
35
+ parameters: connection.parameters.deep_merge(parameters),
36
+ order: { id: 'R12345', state: 'completed' }
37
+ }.to_json)
38
38
  end
39
39
 
40
40
  context 'when basic auth is enabled' do
@@ -42,16 +42,38 @@ module Cangaroo
42
42
 
43
43
  it 'sends key and token as basic auth username and password' do
44
44
  expect(client.class).to receive(:post)
45
- .with(anything, hash_including(basic_auth: {
46
- username: connection.key,
47
- password: connection.token }
48
- ))
45
+ .with(anything, hash_including(basic_auth: { username: connection.key, password: connection.token } ))
49
46
  .and_return(double(response: double(code: '200'), parsed_response: response))
50
47
 
51
48
  client.post(payload, request_id, parameters)
52
49
  end
53
50
  end
54
51
 
52
+ describe 'Rails.configuration.cangaroo.client_timeout' do
53
+
54
+ context 'when set' do
55
+ before { Rails.configuration.cangaroo.client_timeout = 120 }
56
+ it 'sets the timeout config on the client' do
57
+ expect(client.class).to receive(:post)
58
+ .with(anything, hash_including(timeout: 120 ))
59
+ .and_return(double(response: double(code: '200'), parsed_response: response))
60
+
61
+ client.post(payload, request_id, parameters)
62
+ end
63
+ end
64
+
65
+ context 'when not set' do
66
+ before { Rails.configuration.cangaroo.client_timeout = nil }
67
+ it 'does not set the timeout config on the client' do
68
+ allow(client.class).to receive(:post)
69
+ .with(anything, hash_excluding(:timeout))
70
+ .and_return(double(response: double(code: '200'), parsed_response: response))
71
+
72
+ client.post(payload, request_id, parameters)
73
+ end
74
+ end
75
+ end
76
+
55
77
  context 'when response code is 200 (success)' do
56
78
  it 'returns the parsed response' do
57
79
  expect(client.post(payload, request_id, parameters))
@@ -69,16 +91,11 @@ module Cangaroo
69
91
 
70
92
  context 'when response code is not 200 (success)' do
71
93
  let(:failure_response) do
72
- {
73
- 'request_id': '52f367367575e449c3000001',
74
- 'summary': 'Cannot update order. Order R12345 not found in storefront.'
75
- }
94
+ { 'request_id' => '52f367367575e449c3000001', 'summary' => 'Cannot update order. Order R12345 not found in storefront.' }
76
95
  end
77
96
 
78
97
  before do
79
- stub_request(:post, /^#{url}.*/).to_return(
80
- body: failure_response.to_json,
81
- status: 500)
98
+ stub_request(:post, /^#{url}.*/).to_return(body: failure_response.to_json, status: 500)
82
99
  end
83
100
 
84
101
  it 'raises Cangaroo::Webhook::Error' do
@@ -91,9 +108,7 @@ module Cangaroo
91
108
  let(:failure_response) { 'i am not json' }
92
109
 
93
110
  before do
94
- stub_request(:post, /^#{url}.*/).to_return(
95
- body: failure_response,
96
- status: 500)
111
+ stub_request(:post, /^#{url}.*/).to_return(body: failure_response, status: 500)
97
112
  end
98
113
 
99
114
  it 'raises Cangaroo::Webhook::Error' do
@@ -1,11 +1,10 @@
1
1
  require 'simplecov'
2
- require 'codeclimate-test-reporter'
3
2
  require 'pry-byebug'
4
3
 
5
4
  SimpleCov.start 'rails' do
6
5
  add_group 'Commands', 'app/commands'
6
+ add_filter 'lib/cangaroo/version'
7
7
  end
8
- CodeClimate::TestReporter.start
9
8
 
10
9
  # This file is copied to spec/ when you run 'rails generate rspec:install'
11
10
  ENV['RAILS_ENV'] ||= 'test'
@@ -31,6 +30,8 @@ require 'rspec/rails'
31
30
  spec_helpers
32
31
  ).each { |path| require File.expand_path("../support/#{path}.rb", __FILE__) }
33
32
 
33
+ Dir[File.dirname(__FILE__) + '/support/jobs/*.rb'].each { |file| require file }
34
+
34
35
  # Checks for pending migrations before tests are run.
35
36
  # If you are not using ActiveRecord, you can remove this line.
36
37
  ActiveRecord::Migration.maintain_test_schema!
@@ -41,6 +42,7 @@ RSpec.configure do |config|
41
42
  Rails.configuration.cangaroo.basic_auth = false
42
43
  Rails.configuration.cangaroo.jobs = []
43
44
  Rails.configuration.cangaroo.poll_job = []
45
+ Rails.configuration.cangaroo.logger = nil
44
46
  end
45
47
 
46
48
  # The different available types are documented in the features, such as in
@@ -1,51 +1,45 @@
1
1
  require 'rails_helper'
2
2
 
3
3
  module Cangaroo
4
- RSpec.describe EndpointController, type: :controller do
5
- routes { Cangaroo::Engine.routes }
4
+ RSpec.describe :Endpoint, type: :request do
6
5
  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
- end
6
+ let(:request_payload) { load_fixture('json_payload_ok.json') }
7
+ let(:headers) {
8
+ { 'Content-Type' => 'application/json',
9
+ 'X-Hub-Store' => connection.key,
10
+ 'X-Hub-Access-Token' => connection.token }
11
+ }
12
12
 
13
13
  context 'when wombat authentication is enabled' do
14
- before do
15
- request.headers['X-Hub-Store'] = connection.key
16
- request.headers['X-Hub-Access-Token'] = connection.token
17
- end
18
-
19
14
  describe '#create' do
20
15
  before do
21
- post :create, request_payload
16
+ post endpoint_index_path, params: request_payload, headers: headers
22
17
  end
23
18
 
24
19
  it 'accepts only application/json requests' do
25
20
  expect(response.status).to eq(202)
26
21
 
27
- request.headers['Content-Type'] = 'text/html'
28
- post :create
22
+ headers['Content-Type'] = 'text/html'
23
+ post endpoint_index_path, params: {}, headers: headers
29
24
  expect(response.status).to eq(406)
30
25
  end
31
26
 
32
27
  context 'when success' do
33
- let(:auth_headers) {}
34
-
35
28
  it 'responds with 200' do
36
29
  expect(response.status).to eq(202)
37
30
  end
38
31
 
39
32
  it 'responds with the number of objects received in payload' do
40
33
  res = JSON.parse(response.body)
41
- expect(res).to eq('orders' => 2, 'shipments' => 2)
34
+ expect(res).to eq('orders' => 2, 'shipments' => 2,
35
+ 'line_items' => 0, 'line-items' => 0)
42
36
  end
43
37
  end
44
38
 
45
39
  context 'when error' do
46
40
  before do
47
- request.headers['X-Hub-Access-Token'] = 'wrongtoken'
48
- post :create, request_payload
41
+ headers['X-Hub-Access-Token'] = 'wrongtoken'
42
+ post endpoint_index_path, params: request_payload, headers: headers
49
43
  end
50
44
 
51
45
  it 'responds with the command error code' do
@@ -59,8 +53,8 @@ module Cangaroo
59
53
 
60
54
  context 'when an exception was raised' do
61
55
  before do
62
- HandleRequest.stub(:call).and_raise('An error')
63
- post :create, request_payload
56
+ allow(HandleRequest).to receive(:call).and_raise('An error')
57
+ post endpoint_index_path, params: request_payload, headers: headers
64
58
  end
65
59
 
66
60
  it 'responds with 500' do
@@ -81,10 +75,9 @@ module Cangaroo
81
75
 
82
76
  describe '#create' do
83
77
  it 'successfully authorized against a connection key and token' do
84
- request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic
85
- .encode_credentials(connection.key, connection.token)
78
+ headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(connection.key, connection.token)
86
79
 
87
- post :create, request_payload
80
+ post endpoint_index_path, params: request_payload, headers: headers
88
81
 
89
82
  expect(response.status).to eq(202)
90
83
  end
@@ -92,21 +85,19 @@ module Cangaroo
92
85
  it 'successfully authenticates against a connection token' do
93
86
  connection.update(key: '')
94
87
 
95
- request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic
96
- .encode_credentials('', connection.token)
88
+ headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('', connection.token)
97
89
 
98
- post :create, request_payload
90
+ post endpoint_index_path, params: request_payload, headers: headers
99
91
 
100
92
  expect(response.status).to eq(202)
101
93
  end
102
94
 
103
95
  it 'fails to authenticate when basic auth is not provided' do
104
- post :create, request_payload
96
+ post endpoint_index_path, params: request_payload, headers: headers
105
97
 
106
98
  expect(response.status).to eq(401)
107
99
  end
108
100
  end
109
101
  end
110
-
111
102
  end
112
103
  end