cangaroo 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/Rakefile +13 -11
- data/app/controllers/cangaroo/endpoint_controller.rb +24 -4
- data/app/interactors/cangaroo/count_json_object.rb +3 -0
- data/app/interactors/cangaroo/run_polls.rb +12 -0
- data/app/interactors/cangaroo/validate_json_schema.rb +9 -3
- data/app/jobs/cangaroo/job.rb +9 -27
- data/app/jobs/cangaroo/poll_job.rb +67 -0
- data/app/models/cangaroo/connection.rb +10 -2
- data/app/models/cangaroo/poll_timestamp.rb +17 -0
- data/db/migrate/20151030140821_add_parameters_to_cangaroo_connection.rb +1 -1
- data/db/migrate/20160317020230_create_cangaroo_poll_timestamps.rb +12 -0
- data/lib/cangaroo.rb +2 -0
- data/lib/cangaroo/class_configuration.rb +24 -0
- data/lib/cangaroo/engine.rb +2 -0
- data/lib/cangaroo/logger.rb +66 -0
- data/lib/cangaroo/version.rb +1 -1
- data/lib/cangaroo/webhook/client.rb +21 -3
- data/lib/tasks/cangaroo_tasks.rake +7 -4
- data/spec/controllers/cangaroo/endpoint_controller_spec.rb +75 -35
- data/spec/fixtures/json_payload_connection_response.json +1 -0
- data/spec/fixtures/json_payload_empty.json +3 -0
- data/spec/interactors/cangaroo/perform_jobs_spec.rb +28 -13
- data/spec/interactors/cangaroo/run_polls_spec.rb +18 -0
- data/spec/interactors/cangaroo/validate_json_schema_spec.rb +8 -0
- data/spec/jobs/cangaroo/job_spec.rb +12 -1
- data/spec/jobs/cangaroo/poll_job_spec.rb +107 -0
- data/spec/lib/cangaroo/webhook/client_spec.rb +38 -0
- data/spec/rails_helper.rb +18 -44
- data/spec/support/database_cleaner.rb +14 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/rails_app.rb +29 -0
- data/spec/support/shoulda_matchers.rb +8 -0
- data/spec/support/webmock.rb +8 -0
- metadata +71 -87
- data/spec/dummy/README.rdoc +0 -28
- data/spec/dummy/Rakefile +0 -6
- data/spec/dummy/app/assets/javascripts/application.js +0 -13
- data/spec/dummy/app/assets/stylesheets/application.css +0 -15
- data/spec/dummy/app/controllers/application_controller.rb +0 -5
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/app/views/layouts/application.html.erb +0 -14
- data/spec/dummy/bin/bundle +0 -3
- data/spec/dummy/bin/rails +0 -4
- data/spec/dummy/bin/rake +0 -4
- data/spec/dummy/bin/setup +0 -29
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -31
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/database.yml +0 -11
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -41
- data/spec/dummy/config/environments/production.rb +0 -79
- data/spec/dummy/config/environments/test.rb +0 -42
- data/spec/dummy/config/initializers/assets.rb +0 -11
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/dummy/config/initializers/inflections.rb +0 -16
- data/spec/dummy/config/initializers/mime_types.rb +0 -4
- data/spec/dummy/config/initializers/session_store.rb +0 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -9
- data/spec/dummy/config/locales/en.yml +0 -23
- data/spec/dummy/config/routes.rb +0 -4
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +0 -29
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/cangaroo.log +0 -0
- data/spec/dummy/log/development.log +0 -4024
- data/spec/dummy/log/test.log +0 -166964
- data/spec/dummy/public/404.html +0 -67
- data/spec/dummy/public/422.html +0 -67
- data/spec/dummy/public/500.html +0 -66
- data/spec/dummy/public/favicon.ico +0 -0
data/lib/cangaroo/version.rb
CHANGED
@@ -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
|
-
|
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
|
}
|
@@ -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
|
-
|
13
|
+
context 'when wombat authentication is enabled' do
|
16
14
|
before do
|
17
|
-
|
15
|
+
request.headers['X-Hub-Store'] = connection.key
|
16
|
+
request.headers['X-Hub-Access-Token'] = connection.token
|
18
17
|
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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 '
|
24
|
+
it 'accepts only application/json requests' do
|
32
25
|
expect(response.status).to eq(202)
|
33
|
-
end
|
34
26
|
|
35
|
-
|
36
|
-
|
37
|
-
expect(
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
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 '
|
63
|
-
|
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 '
|
67
|
-
|
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
|
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
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.
|
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
|