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.
@@ -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, ROOT + '/transport/upstream_error'
33
- autoload :HostResolutionError, ROOT + '/transport/upstream_error'
34
- autoload :TimeoutError, ROOT + '/transport/upstream_error'
35
- autoload :ConnectionFailedError,ROOT + '/transport/upstream_error'
36
- autoload :InvalidJSONError, ROOT + '/transport/upstream_error'
37
- autoload :HttpError, ROOT + '/transport/http_error'
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 do
8
- Songkick::Transport::Curb.clear_thread_connection
9
- end
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
- begin
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
- before(:each) do
44
- @fake_curl = FakeCurl.new(:error => raised_exception)
45
- end
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) { it_should_raise(Transport::HostResolutionError) }
53
- when_request_raises_the_exception(Curl::Err::ConnectionFailedError){ it_should_raise(Transport::ConnectionFailedError) }
54
- when_request_raises_the_exception(Curl::Err::TimeoutError) { it_should_raise(Transport::TimeoutError) }
55
- when_request_raises_the_exception(Curl::Err::GotNothingError) { it_should_raise(Transport::UpstreamError) }
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.should == exception
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.should =~ pattern
29
- query(get_request, pattern).should == ["access[token]=foo", "password=CK", "username=Louis"]
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.should =~ pattern
37
- query(post_request, pattern).should == ["access[token]=foo", "password=CK", "username=Louis"]
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.stub(:sanitized_params).and_return [/password/, "access[token]", /Authorization/i]
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.should =~ pattern
50
- query(get_request, pattern).should == ["access[token]=[REMOVED]", "password=[REMOVED]", "username=Louis"]
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.should =~ pattern
58
- query(post_request, pattern).should == ["access[token]=[REMOVED]", "password=[REMOVED]", "username=Louis"]
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