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
@@ -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