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
@@ -1,3 +1,3 @@
1
1
  module Cangaroo
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -14,11 +14,29 @@ module Cangaroo
14
14
 
15
15
  def post(payload, request_id, parameters)
16
16
  request_body = body(payload, request_id, parameters).to_json
17
- req = self.class.post(url, headers: headers, body: request_body)
17
+
18
+ request_options = {
19
+ headers: headers,
20
+ body: request_body
21
+ }
22
+
23
+ if Rails.configuration.cangaroo.basic_auth
24
+ request_options.merge!(
25
+ basic_auth: {
26
+ username: connection.key,
27
+ password: connection.token
28
+ }
29
+ )
30
+ end
31
+
32
+ req = self.class.post(url, request_options)
33
+
18
34
  if req.response.code == '200'
19
35
  req.parsed_response
36
+ elsif req.response.code == '204'
37
+ ''
20
38
  else
21
- fail Cangaroo::Webhook::Error, req.parsed_response['summary']
39
+ fail Cangaroo::Webhook::Error, (req.parsed_response['summary'] rescue req.response)
22
40
  end
23
41
  end
24
42
 
@@ -32,7 +50,7 @@ module Cangaroo
32
50
 
33
51
  def headers
34
52
  {
35
- 'X_HUB_TOKEN' => connection.token,
53
+ 'X_HUB_TOKEN' => connection.token || '',
36
54
  'Content-Type' => 'application/json',
37
55
  'Accept' => 'application/json'
38
56
  }
@@ -1,4 +1,7 @@
1
- # desc "Explaining what the task does"
2
- # task :cangaroo do
3
- # # Task goes here
4
- # end
1
+ namespace :cangaroo do
2
+ task poll: :environment do
3
+ Cangaroo::RunPolls.call(
4
+ jobs: Rails.configuration.cangaroo.poll_jobs
5
+ )
6
+ end
7
+ end
@@ -8,65 +8,105 @@ module Cangaroo
8
8
 
9
9
  before do
10
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
11
  end
14
12
 
15
- describe '#create' do
13
+ context 'when wombat authentication is enabled' do
16
14
  before do
17
- post :create, request_payload
15
+ request.headers['X-Hub-Store'] = connection.key
16
+ request.headers['X-Hub-Access-Token'] = connection.token
18
17
  end
19
18
 
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) {}
19
+ describe '#create' do
20
+ before do
21
+ post :create, request_payload
22
+ end
30
23
 
31
- it 'responds with 200' do
24
+ it 'accepts only application/json requests' do
32
25
  expect(response.status).to eq(202)
33
- end
34
26
 
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)
27
+ request.headers['Content-Type'] = 'text/html'
28
+ post :create
29
+ expect(response.status).to eq(406)
38
30
  end
39
- end
40
31
 
41
- context 'when error' do
42
- before do
43
- request.headers['X-Hub-Access-Token'] = 'wrongtoken'
44
- post :create, request_payload
32
+ context 'when success' do
33
+ let(:auth_headers) {}
34
+
35
+ it 'responds with 200' do
36
+ expect(response.status).to eq(202)
37
+ end
38
+
39
+ it 'responds with the number of objects received in payload' do
40
+ res = JSON.parse(response.body)
41
+ expect(res).to eq('orders' => 2, 'shipments' => 2)
42
+ end
45
43
  end
46
44
 
47
- it 'responds with the command error code' do
48
- expect(response.status).to eq(401)
45
+ context 'when error' do
46
+ before do
47
+ request.headers['X-Hub-Access-Token'] = 'wrongtoken'
48
+ post :create, request_payload
49
+ end
50
+
51
+ it 'responds with the command error code' do
52
+ expect(response.status).to eq(401)
53
+ end
54
+
55
+ it 'responds with error messages in the body' do
56
+ expect(JSON.parse(response.body)['error']).to be_present
57
+ end
49
58
  end
50
59
 
51
- it 'responds with error messages in the body' do
52
- expect(JSON.parse(response.body)['error']).to be_present
60
+ context 'when an exception was raised' do
61
+ before do
62
+ HandleRequest.stub(:call).and_raise('An error')
63
+ post :create, request_payload
64
+ end
65
+
66
+ it 'responds with 500' do
67
+ expect(response.status).to eq(500)
68
+ end
69
+
70
+ it 'responds with error messages in the body' do
71
+ expect(JSON.parse(response.body)['error']).to eq 'Something went wrong!'
72
+ end
53
73
  end
54
74
  end
75
+ end
76
+
77
+ context 'when basic auth is enabled' do
78
+ before do
79
+ Rails.configuration.cangaroo.basic_auth = true
80
+ end
81
+
82
+ describe '#create' do
83
+ 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)
55
86
 
56
- context 'when an exception was raised' do
57
- before do
58
- HandleRequest.stub(:call).and_raise('An error')
59
87
  post :create, request_payload
88
+
89
+ expect(response.status).to eq(202)
60
90
  end
61
91
 
62
- it 'responds with 500' do
63
- expect(response.status).to eq(500)
92
+ it 'successfully authenticates against a connection token' do
93
+ connection.update(key: '')
94
+
95
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic
96
+ .encode_credentials('', connection.token)
97
+
98
+ post :create, request_payload
99
+
100
+ expect(response.status).to eq(202)
64
101
  end
65
102
 
66
- it 'responds with error messages in the body' do
67
- expect(JSON.parse(response.body)['error']).to eq 'Something went wrong!'
103
+ it 'fails to authenticate when basic auth is not provided' do
104
+ post :create, request_payload
105
+
106
+ expect(response.status).to eq(401)
68
107
  end
69
108
  end
70
109
  end
110
+
71
111
  end
72
112
  end
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "request_id": "c143e548-afed-44ab-9997-43614d6c762b",
3
3
  "summary": "Product added with Shopify ID of 5775282183 was added.",
4
+ "parameters": {},
4
5
  "products": [
5
6
  {
6
7
  "id": "SKUPRODUCT",
@@ -0,0 +1,3 @@
1
+ {
2
+ "orders": []
3
+ }
@@ -18,25 +18,40 @@ describe Cangaroo::PerformJobs do
18
18
  end
19
19
 
20
20
  describe '.call' do
21
- let(:json_body) { load_fixture('json_payload_ok.json') }
22
-
23
21
  let(:job_a) { double('job_a', perform?: true, enqueue: nil) }
24
22
  let(:job_b) { double('job_b', perform?: false, enqueue: nil) }
25
23
 
26
- it 'instantiates jobs' do
27
- context
28
- expect(JobA).to have_received(:new).exactly(4).times
29
- expect(JobB).to have_received(:new).exactly(4).times
30
- end
24
+ context 'payload with objects' do
25
+ let(:json_body) { load_fixture('json_payload_ok.json') }
26
+
27
+ it 'instantiates jobs' do
28
+ context
29
+ expect(JobA).to have_received(:new).exactly(4).times
30
+ expect(JobB).to have_received(:new).exactly(4).times
31
+ end
32
+
33
+ it 'enqueues only cangaroo jobs that can perform' do
34
+ context
35
+ expect(job_a).to have_received(:enqueue).exactly(4).times
36
+ expect(job_b).to_not have_received(:enqueue)
37
+ end
31
38
 
32
- it 'enqueues only cangaroo jobs that can perform' do
33
- context
34
- expect(job_a).to have_received(:enqueue).exactly(4).times
35
- expect(job_b).to_not have_received(:enqueue)
39
+ it 'succeeds' do
40
+ expect(context).to be_a_success
41
+ end
36
42
  end
37
43
 
38
- it 'succeeds' do
39
- expect(context).to be_a_success
44
+ context 'payload with no objects' do
45
+ let(:json_body) { load_fixture('json_payload_empty.json') }
46
+
47
+ it 'succeeds' do
48
+ context
49
+
50
+ expect(context).to be_a_success
51
+ expect(job_a).to_not have_received(:enqueue)
52
+ expect(job_b).to_not have_received(:enqueue)
53
+ end
40
54
  end
55
+
41
56
  end
42
57
  end
@@ -0,0 +1,18 @@
1
+ require 'rails_helper'
2
+
3
+ class PollJobA < Cangaroo::PollJob; end
4
+ class PollJobB < Cangaroo::PollJob; end
5
+
6
+ describe Cangaroo::RunPolls do
7
+ let(:connection) { create(:cangaroo_connection) }
8
+
9
+ # let(:job_a) { double('job_a', enqueue: nil) }
10
+ # let(:job_b) { double('job_b', enqueue: nil) }
11
+
12
+ it 'enques all polling jobs' do
13
+ expect_any_instance_of(PollJobA).to receive(:enqueue).once
14
+ expect_any_instance_of(PollJobB).to receive(:enqueue).once
15
+
16
+ Cangaroo::RunPolls.call(jobs: [PollJobA, PollJobB])
17
+ end
18
+ end
@@ -12,6 +12,14 @@ describe Cangaroo::ValidateJsonSchema do
12
12
  end
13
13
  end
14
14
 
15
+ context 'when json is well formatted and empty' do
16
+ let(:json_body) { parse_fixture('json_payload_empty.json') }
17
+
18
+ it 'succeeds' do
19
+ expect(context).to be_a_success
20
+ end
21
+ end
22
+
15
23
  context 'when json is well formatted and come from a connection' do
16
24
  let(:json_body) { parse_fixture('json_payload_connection_response.json') }
17
25
 
@@ -25,7 +25,7 @@ module Cangaroo
25
25
  end
26
26
 
27
27
  before do
28
- client.stub(:post).and_return(connection_response)
28
+ allow(client).to receive(:post).and_return(connection_response)
29
29
  allow(Cangaroo::Webhook::Client).to receive(:new).and_return(client)
30
30
  allow(Cangaroo::PerformFlow).to receive(:call)
31
31
  end
@@ -49,10 +49,21 @@ module Cangaroo
49
49
  it 'restart the flow' do
50
50
  job.perform
51
51
  expect(Cangaroo::PerformFlow).to have_received(:call)
52
+ .once
52
53
  .with(source_connection: destination_connection,
53
54
  json_body: connection_response.to_json,
54
55
  jobs: Rails.configuration.cangaroo.jobs)
55
56
  end
57
+
58
+ context 'endpoint provides a empty response' do
59
+ it 'should not restart the flow' do
60
+ allow(client).to receive(:post).and_return('')
61
+
62
+ job.perform
63
+
64
+ expect(Cangaroo::PerformFlow).to_not have_received(:call)
65
+ end
66
+ end
56
67
  end
57
68
 
58
69
  describe '#perform?' do
@@ -0,0 +1,107 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe Cangaroo::PollJob, type: :job do
4
+ class FakePollJob < Cangaroo::PollJob
5
+ connection :store
6
+ path '/webhook_path'
7
+ parameters(email: 'info@nebulab.it')
8
+ end
9
+
10
+ let(:job_class) { FakePollJob }
11
+ let(:job) { job_class.new({ last_poll: Time.now.to_i }) }
12
+ let(:successful_pull_payload) { parse_fixture('json_payload_ok.json') }
13
+
14
+ before do
15
+ create(:cangaroo_connection)
16
+ end
17
+
18
+ describe '#perform?' do
19
+ before do
20
+ allow_any_instance_of(Cangaroo::Webhook::Client).to receive(:post)
21
+ .and_return(successful_pull_payload)
22
+
23
+ allow(Cangaroo::HandleRequest).to receive(:call).
24
+ and_return(double(success?: true))
25
+ end
26
+
27
+ it 'runs a poll if one has never been run' do
28
+ expect(FakePollJob.new.perform?(DateTime.now)).to eq(true)
29
+ end
30
+
31
+ it 'runs a poll if the poll frequency delta is reached' do
32
+ last_poll = Cangaroo::PollTimestamp.for_class(FakePollJob)
33
+ last_poll.value = DateTime.now - FakePollJob.frequency - 1.second
34
+ last_poll.save
35
+
36
+ expect(FakePollJob.new.perform?(DateTime.now)).to eq(true)
37
+ end
38
+
39
+ it 'does not run a poll if the time passed is less than the frequency' do
40
+ last_poll = Cangaroo::PollTimestamp.for_class(FakePollJob)
41
+ last_poll.value = DateTime.now - FakePollJob.frequency + 1.second
42
+ last_poll.save
43
+
44
+ expect(FakePollJob.new.perform?(DateTime.now)).to eq(false)
45
+ end
46
+ end
47
+
48
+ describe '#perform' do
49
+
50
+ context 'pull is successful' do
51
+
52
+ before do
53
+ allow(Cangaroo::HandleRequest).to receive(:call).
54
+ and_return(double(success?: true))
55
+
56
+ allow_any_instance_of(Cangaroo::PollJob).to receive(:perform?)
57
+ .and_return(true)
58
+ end
59
+
60
+ it 'updates the poll timestamp' do
61
+ Cangaroo::Webhook::Client.any_instance.stub(:post).and_return(successful_pull_payload)
62
+
63
+ job.perform
64
+
65
+ last_poll_timestamp = Cangaroo::PollTimestamp.for_class(FakePollJob)
66
+ expect(last_poll_timestamp.job).to eq(job.class.to_s)
67
+ expect(last_poll_timestamp.connection.name).to eq(job.class.connection.to_s)
68
+ expect(last_poll_timestamp.value).to be <= DateTime.now
69
+ end
70
+
71
+ it 'handles a empty response' do
72
+ Cangaroo::Webhook::Client.any_instance.stub(:post).and_return('')
73
+
74
+ job.perform
75
+ end
76
+
77
+ it 'handles a response with a empty array' do
78
+ Cangaroo::Webhook::Client.any_instance.stub(:post).and_return(
79
+ parse_fixture('json_payload_empty.json')
80
+ )
81
+
82
+ job.perform
83
+ end
84
+
85
+ end
86
+
87
+ context 'pull fails' do
88
+
89
+ before do
90
+ Cangaroo::Webhook::Client.any_instance.stub(:post).and_return(parse_fixture('json_payload_ok.json'))
91
+
92
+ allow(Cangaroo::HandleRequest).to receive(:call).and_return(double(
93
+ success?: false,
94
+ message: 'bad failure'
95
+ ))
96
+ end
97
+
98
+ it 'does not update timestamp if pull fails' do
99
+ expect { job.perform }.to raise_error(Cangaroo::Webhook::Error)
100
+
101
+ expect(Cangaroo::PollTimestamp.for_class(FakePollJob).id).to be_nil
102
+ end
103
+
104
+ end
105
+ end
106
+
107
+ end
@@ -37,6 +37,21 @@ module Cangaroo
37
37
  }.to_json)
38
38
  end
39
39
 
40
+ context 'when basic auth is enabled' do
41
+ before { Rails.configuration.cangaroo.basic_auth = true }
42
+
43
+ it 'sends key and token as basic auth username and password' do
44
+ expect(client.class).to receive(:post)
45
+ .with(anything, hash_including(basic_auth: {
46
+ username: connection.key,
47
+ password: connection.token }
48
+ ))
49
+ .and_return(double(response: double(code: '200'), parsed_response: response))
50
+
51
+ client.post(payload, request_id, parameters)
52
+ end
53
+ end
54
+
40
55
  context 'when response code is 200 (success)' do
41
56
  it 'returns the parsed response' do
42
57
  expect(client.post(payload, request_id, parameters))
@@ -44,6 +59,14 @@ module Cangaroo
44
59
  end
45
60
  end
46
61
 
62
+ context 'when response code is 204 (no content)' do
63
+ it 'returns an empty string' do
64
+ stub_request(:post, /^#{url}.*/).to_return(status: 204, body: '')
65
+
66
+ expect(client.post(payload, request_id, parameters)).to eq('')
67
+ end
68
+ end
69
+
47
70
  context 'when response code is not 200 (success)' do
48
71
  let(:failure_response) do
49
72
  {
@@ -63,6 +86,21 @@ module Cangaroo
63
86
  .to raise_error(Cangaroo::Webhook::Error)
64
87
  end
65
88
  end
89
+
90
+ context 'when response code is not 200 and response is not json' do
91
+ let(:failure_response) { 'i am not json' }
92
+
93
+ before do
94
+ stub_request(:post, /^#{url}.*/).to_return(
95
+ body: failure_response,
96
+ status: 500)
97
+ end
98
+
99
+ it 'raises Cangaroo::Webhook::Error' do
100
+ expect { client.post(payload, request_id, parameters) }
101
+ .to raise_error(Cangaroo::Webhook::Error)
102
+ end
103
+ end
66
104
  end
67
105
  end
68
106
  end