songkick-transport 1.2.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +52 -15
- data/lib/songkick/transport/authentication.rb +27 -0
- data/lib/songkick/transport/base.rb +146 -19
- data/lib/songkick/transport/curb.rb +31 -19
- data/lib/songkick/transport/headers.rb +15 -10
- data/lib/songkick/transport/html_report.html.erb +6 -2
- data/lib/songkick/transport/httparty.rb +15 -21
- data/lib/songkick/transport/rack_test.rb +20 -35
- data/lib/songkick/transport/reporting.rb +1 -2
- data/lib/songkick/transport/request.rb +17 -18
- data/lib/songkick/transport/response.rb +6 -7
- data/lib/songkick/transport/service.rb +51 -14
- data/lib/songkick/transport.rb +27 -25
- data/spec/songkick/transport/authentication_spec.rb +28 -0
- data/spec/songkick/transport/base_spec.rb +146 -0
- data/spec/songkick/transport/curb_spec.rb +16 -38
- data/spec/songkick/transport/httparty_spec.rb +8 -8
- data/spec/songkick/transport/request_spec.rb +9 -9
- data/spec/songkick/transport/response_spec.rb +12 -12
- data/spec/songkick/transport/service_spec.rb +70 -0
- data/spec/songkick/transport_spec.rb +168 -58
- data/spec/spec_helper.rb +21 -12
- metadata +79 -43
- data/lib/songkick/transport/header_decorator.rb +0 -27
- data/lib/songkick/transport/timeout_decorator.rb +0 -27
data/lib/songkick/transport.rb
CHANGED
@@ -9,40 +9,43 @@ module Songkick
|
|
9
9
|
DEFAULT_TIMEOUT = 5
|
10
10
|
DEFAULT_FORMAT = :json
|
11
11
|
DEFAULT_USER_ERROR_CODES = [409]
|
12
|
-
|
12
|
+
|
13
13
|
HTTP_VERBS = %w[options head get patch post put delete]
|
14
14
|
USE_BODY = %w[post put]
|
15
15
|
FORM_ENCODING = 'application/x-www-form-urlencoded'
|
16
|
-
|
16
|
+
|
17
17
|
ROOT = File.expand_path('..', __FILE__)
|
18
|
-
|
18
|
+
|
19
19
|
autoload :Serialization, ROOT + '/transport/serialization'
|
20
|
+
autoload :Authentication, ROOT + '/transport/authentication'
|
20
21
|
autoload :Base, ROOT + '/transport/base'
|
21
22
|
autoload :Curb, ROOT + '/transport/curb'
|
22
23
|
autoload :Headers, ROOT + '/transport/headers'
|
23
|
-
autoload :HeaderDecorator, ROOT + '/transport/header_decorator'
|
24
24
|
autoload :HttParty, ROOT + '/transport/httparty'
|
25
25
|
autoload :RackTest, ROOT + '/transport/rack_test'
|
26
26
|
autoload :Reporting, ROOT + '/transport/reporting'
|
27
27
|
autoload :Request, ROOT + '/transport/request'
|
28
28
|
autoload :Response, ROOT + '/transport/response'
|
29
|
-
autoload :TimeoutDecorator, ROOT + '/transport/timeout_decorator'
|
30
29
|
autoload :Service, ROOT + '/transport/service'
|
31
30
|
|
32
|
-
autoload :UpstreamError,
|
33
|
-
autoload :HostResolutionError,
|
34
|
-
autoload :TimeoutError,
|
35
|
-
autoload :ConnectionFailedError,ROOT + '/transport/upstream_error'
|
36
|
-
autoload :InvalidJSONError,
|
37
|
-
autoload :HttpError,
|
31
|
+
autoload :UpstreamError, ROOT + '/transport/upstream_error'
|
32
|
+
autoload :HostResolutionError, ROOT + '/transport/upstream_error'
|
33
|
+
autoload :TimeoutError, ROOT + '/transport/upstream_error'
|
34
|
+
autoload :ConnectionFailedError, ROOT + '/transport/upstream_error'
|
35
|
+
autoload :InvalidJSONError, ROOT + '/transport/upstream_error'
|
36
|
+
autoload :HttpError, ROOT + '/transport/http_error'
|
38
37
|
|
39
38
|
def self.register_parser(content_type, parser)
|
40
39
|
@parsers ||= {}
|
41
40
|
@parsers[content_type] = parser
|
42
41
|
end
|
43
42
|
|
43
|
+
def self.register_default_parser(parser)
|
44
|
+
@default_parser = parser
|
45
|
+
end
|
46
|
+
|
44
47
|
def self.parser_for(content_type)
|
45
|
-
parser = @parsers && @parsers[content_type]
|
48
|
+
parser = (@parsers && @parsers[content_type]) || @default_parser
|
46
49
|
unless parser
|
47
50
|
raise TypeError, "Could not find a parser for content-type: #{content_type}"
|
48
51
|
end
|
@@ -50,52 +53,51 @@ module Songkick
|
|
50
53
|
end
|
51
54
|
|
52
55
|
register_parser 'application/json', Yajl::Parser
|
53
|
-
|
56
|
+
|
54
57
|
IO = UploadIO
|
55
|
-
|
58
|
+
|
56
59
|
def self.io(object)
|
57
60
|
if Hash === object and [:tempfile, :type, :filename].all? { |k| object.has_key? k } # Rack upload
|
58
61
|
Transport::IO.new(object[:tempfile], object[:type], object[:filename])
|
59
|
-
|
62
|
+
|
60
63
|
elsif object.respond_to?(:content_type) and object.respond_to?(:original_filename) # Rails upload
|
61
64
|
Transport::IO.new(object, object.content_type, object.original_filename)
|
62
|
-
|
65
|
+
|
63
66
|
else
|
64
67
|
raise ArgumentError, "Could not generate a Transport::IO from #{object.inspect}"
|
65
68
|
end
|
66
69
|
end
|
67
|
-
|
70
|
+
|
68
71
|
def self.logger
|
69
72
|
@logger ||= begin
|
70
73
|
require 'logger'
|
71
74
|
Logger.new(STDOUT)
|
72
75
|
end
|
73
76
|
end
|
74
|
-
|
77
|
+
|
75
78
|
def self.logger=(logger)
|
76
79
|
@logger = logger
|
77
80
|
end
|
78
|
-
|
81
|
+
|
79
82
|
def self.verbose=(verbose)
|
80
83
|
@verbose = verbose
|
81
84
|
end
|
82
|
-
|
85
|
+
|
83
86
|
def self.verbose?
|
84
87
|
@verbose
|
85
88
|
end
|
86
|
-
|
89
|
+
|
87
90
|
def self.report
|
88
91
|
Reporting.report
|
89
92
|
end
|
90
|
-
|
93
|
+
|
91
94
|
def self.sanitize(*params)
|
92
95
|
sanitized_params.concat(params)
|
93
96
|
end
|
94
|
-
|
97
|
+
|
95
98
|
def self.sanitized_params
|
96
99
|
@sanitized_params ||= []
|
97
100
|
end
|
98
|
-
|
101
|
+
|
99
102
|
end
|
100
103
|
end
|
101
|
-
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Songkick::Transport::Authentication do
|
4
|
+
def basic_auth_headers(*args)
|
5
|
+
Songkick::Transport::Authentication.basic_auth_headers(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def strict_encode64(*args)
|
9
|
+
Songkick::Transport::Authentication.strict_encode64(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "given basic auth credentials" do
|
13
|
+
let(:credentials) { {:username => "foo", :password => "baz"} }
|
14
|
+
let(:headers) { basic_auth_headers(credentials) }
|
15
|
+
let(:encoded_credendials) { strict_encode64 "#{credentials[:username]}:#{credentials[:password]}"}
|
16
|
+
|
17
|
+
it "encodes the credentials correctly" do
|
18
|
+
expected_encoded_credentials = "Zm9vOmJheg=="
|
19
|
+
expect(encoded_credendials).to eq(expected_encoded_credentials)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "gets correct headers" do
|
23
|
+
expected_auth_headers = Songkick::Transport::Headers.new({"Authorization" => "Basic Zm9vOmJheg=="})
|
24
|
+
expect(headers).to eq(expected_auth_headers)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Songkick::Transport::Base do
|
4
|
+
let(:host) { 'base' }
|
5
|
+
subject { described_class.new(host) }
|
6
|
+
|
7
|
+
|
8
|
+
describe 'Instrumentation' do
|
9
|
+
subject { described_class.new(host, options) }
|
10
|
+
|
11
|
+
let(:options) do
|
12
|
+
{ :instrumenter => ActiveSupport::Notifications }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'given a custom instrumentation label' do
|
16
|
+
before { options[:instrumentation_label] = 'a.custom.label' }
|
17
|
+
|
18
|
+
it 'instruments the request with the custom label' do
|
19
|
+
allow(subject).to receive(:execute_request)
|
20
|
+
expect(options[:instrumenter]).to receive(:instrument).with(options[:instrumentation_label], anything)
|
21
|
+
subject.get('/')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'Basic auth' do
|
27
|
+
subject { described_class.new(host, options) }
|
28
|
+
let(:options) {{}}
|
29
|
+
|
30
|
+
context 'given basic auth credentials' do
|
31
|
+
before { options[:basic_auth] = {:username => "foo", :password => "baz"} }
|
32
|
+
|
33
|
+
it 'should add basic auth credentials to subsequent requests' do
|
34
|
+
expect(subject).to receive(:execute_request) do |request|
|
35
|
+
expected_headers = {"Authorization"=>"Basic Zm9vOmJheg=="}
|
36
|
+
expect(request.headers).to include(expected_headers)
|
37
|
+
Songkick::Transport::Response::NoContent.new(204, {}, "")
|
38
|
+
end
|
39
|
+
|
40
|
+
subject.get('/')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Decoration" do
|
46
|
+
|
47
|
+
it "should let you add headers" do
|
48
|
+
decorated_http = subject.with_headers("A" => "B")
|
49
|
+
|
50
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new("A" => "B"), nil)
|
51
|
+
|
52
|
+
decorated_http.get("/")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should let you add headers but then override them" do
|
56
|
+
decorated_http = subject.with_headers("A" => "B")
|
57
|
+
|
58
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new("A" => "A2", "C" => "D"), nil)
|
59
|
+
|
60
|
+
decorated_http.get("/", {}, {"A" => "A2", "C" => "D"})
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should let you add headers multiple times and combine them" do
|
64
|
+
decorated_http = subject.with_headers("A" => "B").with_headers("C" => "D")
|
65
|
+
|
66
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new("A" => "B", "C" => "D"), nil)
|
67
|
+
|
68
|
+
decorated_http.get("/")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should let you add timeouts" do
|
72
|
+
decorated_http = subject.with_timeout(10)
|
73
|
+
|
74
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, {}, 10)
|
75
|
+
|
76
|
+
decorated_http.get("/")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should let you add a timeout but then override it" do
|
80
|
+
decorated_http = subject.with_timeout(10)
|
81
|
+
|
82
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, {}, 20)
|
83
|
+
|
84
|
+
decorated_http.get("/", {}, {}, 20)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should let you add timeouts multiple times and take the last" do
|
88
|
+
decorated_http = subject.with_timeout(10).with_timeout(20).with_timeout(30)
|
89
|
+
|
90
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, {}, 30)
|
91
|
+
|
92
|
+
decorated_http.get("/")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should let you add headers and timeouts" do
|
96
|
+
decorated_http = subject.with_headers("A" => "B").with_timeout(10)
|
97
|
+
|
98
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new("A" => "B"), 10)
|
99
|
+
|
100
|
+
decorated_http.get("/")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should let you add params" do
|
104
|
+
decorated_http = subject.with_params("admin" => "true")
|
105
|
+
|
106
|
+
expect(subject).to receive(:do_verb).with("get", "/", {"admin" => "true"}, {}, nil)
|
107
|
+
|
108
|
+
decorated_http.get("/")
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should let you add params multiple times and merge them" do
|
112
|
+
decorated_http = subject.with_params("admin" => "true").with_params("foo" => "123")
|
113
|
+
|
114
|
+
expect(subject).to receive(:do_verb).with("get", "/", {"admin" => "true", "foo" => "123"}, {}, nil)
|
115
|
+
|
116
|
+
decorated_http.get("/")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should let you add basic authorization" do
|
120
|
+
decorated_http = subject.with_basic_auth({:username => "foo", :password => "bar"})
|
121
|
+
expected_auth_headers = {"Authorization" => "Basic Zm9vOmJhcg=="}
|
122
|
+
|
123
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new(expected_auth_headers), nil)
|
124
|
+
|
125
|
+
decorated_http.get("/")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should let you add basic authorization and merge them with other headers created by HeaderDecorator" do
|
129
|
+
decorated_http = subject.with_headers({"hello" => "world"}).with_basic_auth({:username => "foo", :password => "bar"})
|
130
|
+
expected_headers = {"Authorization" => "Basic Zm9vOmJhcg==", "hello" => "world"}
|
131
|
+
|
132
|
+
expect(subject).to receive(:do_verb).with("get", "/", {}, Songkick::Transport::Headers.new(expected_headers), nil)
|
133
|
+
|
134
|
+
decorated_http.get("/")
|
135
|
+
end
|
136
|
+
|
137
|
+
it "THE LOT" do
|
138
|
+
decorated_http = subject.with_basic_auth({:username => "foo", :password => "bar"}).with_headers("Version" => "3.0").with_params("foo" => "123").with_timeout(10)
|
139
|
+
|
140
|
+
expect(subject).to receive(:do_verb).with("get", "/", {"foo" => "123"}, Songkick::Transport::Headers.new("Version" => "3.0", "Authorization" => "Basic Zm9vOmJhcg=="), 10)
|
141
|
+
|
142
|
+
decorated_http.get("/")
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -2,59 +2,37 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Songkick
|
4
4
|
module Transport
|
5
|
-
|
5
|
+
|
6
6
|
describe Curb do
|
7
|
-
after
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
class FakeCurl
|
12
|
-
attr_writer :url, :timeout
|
13
|
-
attr_reader :on_header, :response_code, :body_str, :headers
|
14
|
-
|
15
|
-
def initialize(options)
|
16
|
-
@error = options[:error]
|
17
|
-
@headers = {}
|
18
|
-
end
|
19
|
-
|
20
|
-
def http(verb)
|
21
|
-
raise(@error, "bang") if @error
|
22
|
-
end
|
23
|
-
|
24
|
-
def reset
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
subject{ Curb.new('localhost', :connection => @fake_curl) }
|
29
|
-
let(:request){ Request.new('http://localhost', 'get', '/', {}) }
|
7
|
+
after { described_class.clear_thread_connection }
|
8
|
+
|
9
|
+
subject { described_class.new('localhost', :connection => curl) }
|
10
|
+
let(:request) { Request.new('http://localhost', 'get', '/', {}) }
|
30
11
|
|
31
12
|
def self.it_should_raise(exception)
|
32
13
|
it "should raise error #{exception}" do
|
33
|
-
|
34
|
-
subject.execute_request(request)
|
35
|
-
rescue => e
|
36
|
-
e.class.should == exception
|
37
|
-
end
|
14
|
+
expect { subject.execute_request(request) }.to raise_error(exception)
|
38
15
|
end
|
39
16
|
end
|
40
17
|
|
41
18
|
def self.when_request_raises_the_exception(raised_exception, &block)
|
42
19
|
describe "when request raises a #{raised_exception}" do
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
20
|
+
let(:curl) { instance_double(Curl::Easy, :headers => {}).as_null_object }
|
21
|
+
|
22
|
+
before { allow(curl).to receive(:http).and_raise(raised_exception) }
|
23
|
+
|
47
24
|
class_exec(&block)
|
48
25
|
end
|
49
26
|
end
|
50
27
|
|
51
28
|
describe "handling errors" do
|
52
|
-
when_request_raises_the_exception(Curl::Err::HostResolutionError)
|
53
|
-
when_request_raises_the_exception(Curl::Err::ConnectionFailedError){ it_should_raise(Transport::ConnectionFailedError) }
|
54
|
-
when_request_raises_the_exception(Curl::Err::TimeoutError)
|
55
|
-
when_request_raises_the_exception(Curl::Err::GotNothingError)
|
29
|
+
when_request_raises_the_exception(Curl::Err::HostResolutionError) { it_should_raise(Transport::HostResolutionError) }
|
30
|
+
when_request_raises_the_exception(Curl::Err::ConnectionFailedError) { it_should_raise(Transport::ConnectionFailedError) }
|
31
|
+
when_request_raises_the_exception(Curl::Err::TimeoutError) { it_should_raise(Transport::TimeoutError) }
|
32
|
+
when_request_raises_the_exception(Curl::Err::GotNothingError) { it_should_raise(Transport::UpstreamError) }
|
33
|
+
when_request_raises_the_exception(Curl::Err::RecvError) { it_should_raise(Transport::UpstreamError) }
|
56
34
|
end
|
57
35
|
end
|
58
|
-
|
36
|
+
|
59
37
|
end
|
60
38
|
end
|
@@ -2,30 +2,30 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Songkick
|
4
4
|
module Transport
|
5
|
-
|
5
|
+
|
6
6
|
describe HttParty do
|
7
7
|
class FakeJSONException < Exception; end
|
8
8
|
|
9
9
|
let(:request){ Request.new('http://localhost', 'get', '/', {}) }
|
10
|
-
|
10
|
+
|
11
11
|
describe "handling errors" do
|
12
12
|
class FakeHttparty < Songkick::Transport::HttParty::Adapter
|
13
13
|
class << self
|
14
14
|
attr_accessor :error
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def self.get(path, args)
|
18
18
|
raise(error, "bang") if error
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def self.it_should_raise(exception)
|
24
24
|
it "should raise error #{exception}" do
|
25
25
|
begin
|
26
26
|
@httparty.execute_request(request)
|
27
27
|
rescue => e
|
28
|
-
e.class.
|
28
|
+
expect(e.class).to eq(exception)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -36,7 +36,7 @@ module Songkick
|
|
36
36
|
FakeHttparty.error = raised_exception
|
37
37
|
@httparty = Songkick::Transport::HttParty.new('localhost', {:adapter => FakeHttparty})
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
class_exec(&block)
|
41
41
|
end
|
42
42
|
end
|
@@ -50,6 +50,6 @@ module Songkick
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
end
|
55
55
|
end
|
@@ -25,37 +25,37 @@ describe Songkick::Transport::Request do
|
|
25
25
|
context "with a get request" do
|
26
26
|
it "returns the request as a curl command" do
|
27
27
|
pattern = %r{^GET 'www.example.com/\?([^']+)' -H 'Authorization: Hello'$}
|
28
|
-
get_request.to_s.
|
29
|
-
query(get_request, pattern).
|
28
|
+
expect(get_request.to_s).to match(pattern)
|
29
|
+
expect(query(get_request, pattern)).to eq(["access[token]=foo", "password=CK", "username=Louis"])
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
context "with a post request" do
|
34
34
|
it "returns the request as a curl command" do
|
35
35
|
pattern = %r{^POST 'www.example.com/' -H 'Content-Type: application/x-www-form-urlencoded' -d '([^']+)'$}
|
36
|
-
post_request.to_s.
|
37
|
-
query(post_request, pattern).
|
36
|
+
expect(post_request.to_s).to match(pattern)
|
37
|
+
expect(query(post_request, pattern)).to eq(["access[token]=foo", "password=CK", "username=Louis"])
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
describe "with query sanitization" do
|
42
42
|
before do
|
43
|
-
Songkick::Transport.
|
43
|
+
allow(Songkick::Transport).to receive(:sanitized_params).and_return [/password/, "access[token]", /Authorization/i]
|
44
44
|
end
|
45
45
|
|
46
46
|
context "with a get request" do
|
47
47
|
it "removes the parameter values from the request" do
|
48
48
|
pattern = %r{^GET 'www.example.com/\?([^']+)' -H 'Authorization: \[REMOVED\]'$}
|
49
|
-
get_request.to_s.
|
50
|
-
query(get_request, pattern).
|
49
|
+
expect(get_request.to_s).to match(pattern)
|
50
|
+
expect(query(get_request, pattern)).to eq(["access[token]=[REMOVED]", "password=[REMOVED]", "username=Louis"])
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
context "with a post request" do
|
55
55
|
it "removes the parameter values from the request" do
|
56
56
|
pattern = %r{^POST 'www.example.com/' -H 'Content-Type: application/x-www-form-urlencoded' -d '([^']+)'$}
|
57
|
-
post_request.to_s.
|
58
|
-
query(post_request, pattern).
|
57
|
+
expect(post_request.to_s).to match(pattern)
|
58
|
+
expect(query(post_request, pattern)).to eq(["access[token]=[REMOVED]", "password=[REMOVED]", "username=Louis"])
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|