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,11 +9,11 @@ describe Songkick::Transport::Response do
9
9
  let(:response) { process("", 200, {"Content-Type" => "application/json; charset=utf-8"}, '{"hello":"world"}') }
10
10
 
11
11
  it "is an OK" do
12
- response.should be_a(Songkick::Transport::Response::OK)
12
+ expect(response).to be_a(Songkick::Transport::Response::OK)
13
13
  end
14
14
 
15
15
  it "exposes its data" do
16
- response.data.should == {"hello" => "world"}
16
+ expect(response.data).to eq({"hello" => "world"})
17
17
  end
18
18
  end
19
19
 
@@ -21,11 +21,11 @@ describe Songkick::Transport::Response do
21
21
  let(:response) { process("", 200, {"Content-Type" => "application/json"}, "{\"hello\":\"world\"\n\n}") }
22
22
 
23
23
  it "is an OK" do
24
- response.should be_a(Songkick::Transport::Response::OK)
24
+ expect(response).to be_a(Songkick::Transport::Response::OK)
25
25
  end
26
26
 
27
27
  it "exposes its data" do
28
- response.data.should == {"hello" => "world"}
28
+ expect(response.data).to eq({"hello" => "world"})
29
29
  end
30
30
  end
31
31
 
@@ -33,7 +33,7 @@ describe Songkick::Transport::Response do
33
33
  let(:response) { process("", 200, {"Content-Type" => "application/json"}, {"hello" => "world"}) }
34
34
 
35
35
  it "exposes its data" do
36
- response.data.should == {"hello" => "world"}
36
+ expect(response.data).to eq({"hello" => "world"})
37
37
  end
38
38
  end
39
39
 
@@ -41,7 +41,7 @@ describe Songkick::Transport::Response do
41
41
  let(:response) { process("", 200, {}, "") }
42
42
 
43
43
  it "exposes its data" do
44
- response.data.should be_nil
44
+ expect(response.data).to be_nil
45
45
  end
46
46
  end
47
47
 
@@ -49,7 +49,7 @@ describe Songkick::Transport::Response do
49
49
  let(:response) { process("", 201, {}, "") }
50
50
 
51
51
  it "is a Created" do
52
- response.should be_a(Songkick::Transport::Response::Created)
52
+ expect(response).to be_a(Songkick::Transport::Response::Created)
53
53
  end
54
54
  end
55
55
 
@@ -57,7 +57,7 @@ describe Songkick::Transport::Response do
57
57
  let(:response) { process("", 204, {}, "") }
58
58
 
59
59
  it "is a NoContent" do
60
- response.should be_a(Songkick::Transport::Response::NoContent)
60
+ expect(response).to be_a(Songkick::Transport::Response::NoContent)
61
61
  end
62
62
  end
63
63
 
@@ -65,11 +65,11 @@ describe Songkick::Transport::Response do
65
65
  let(:response) { process("", 409, {"Content-Type" => "application/json"}, '{"errors":[]}') }
66
66
 
67
67
  it "is a UserError" do
68
- response.should be_a(Songkick::Transport::Response::UserError)
68
+ expect(response).to be_a(Songkick::Transport::Response::UserError)
69
69
  end
70
70
 
71
71
  it "exposes the errors" do
72
- response.errors.should == []
72
+ expect(response.errors).to eq([])
73
73
  end
74
74
  end
75
75
 
@@ -77,11 +77,11 @@ describe Songkick::Transport::Response do
77
77
  let(:response) { process("", 422, {"Content-Type" => "application/json"}, '{"errors":[]}', [409, 422]) }
78
78
 
79
79
  it "is a UserError" do
80
- response.should be_a(Songkick::Transport::Response::UserError)
80
+ expect(response).to be_a(Songkick::Transport::Response::UserError)
81
81
  end
82
82
 
83
83
  it "exposes the errors" do
84
- response.errors.should == []
84
+ expect(response.errors).to eq([])
85
85
  end
86
86
  end
87
87
  end
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+
3
+ describe Songkick::Transport::Service do
4
+ let(:http) { double(described_class::DEFAULT_TRANSPORT) }
5
+
6
+ before do
7
+ described_class.set_endpoints 'foo' => 'nonsuch:1111'
8
+ described_class.user_agent described_class.to_s
9
+
10
+ allow(described_class::DEFAULT_TRANSPORT).to receive(:new).and_return(http)
11
+ allow(http).to receive(:with_headers).and_return(http)
12
+ end
13
+
14
+ describe "given a Service class hierarchy" do
15
+ class A < Songkick::Transport::Service
16
+ endpoint :foo
17
+ end
18
+
19
+ class B < A
20
+ endpoint :foo
21
+ end
22
+
23
+ it "should inherit headers all the way down the class hierarchy" do
24
+ Songkick::Transport::Service.with_headers "S" => "sss"
25
+ B.with_headers "B" => "bbb"
26
+ A.with_headers "A" => "aaa"
27
+
28
+ expect(http).to receive(:with_headers).with("S" => "sss", "B" => "bbb", "A" => "aaa")
29
+ B.new.http
30
+ end
31
+
32
+ it "can change headers" do
33
+ Songkick::Transport::Service.with_headers "S" => "sss"
34
+ Songkick::Transport::Service.with_headers "STS" => "sts"
35
+ B.with_headers "B" => "bbb"
36
+ A.with_headers "A" => "aaa"
37
+
38
+ expect(http).to receive(:with_headers).with("STS" => "sts", "B" => "bbb", "A" => "aaa")
39
+ B.new.http
40
+ end
41
+
42
+ context 'with transport layer options' do
43
+ let(:global_options) { {:foo => 'bar'} }
44
+ let(:upper_options) { {:bar => 'baz'} }
45
+ let(:lower_options) { {:bar => 'qux'} }
46
+
47
+ before do
48
+ Songkick::Transport::Service.transport_layer_options global_options
49
+ A.transport_layer_options upper_options
50
+ B.transport_layer_options lower_options
51
+ end
52
+
53
+ it "global options can be specified and are passed to the transport initializer" do
54
+ expect(described_class::DEFAULT_TRANSPORT).to receive(:new).with(anything, hash_including(global_options))
55
+ B.new.http
56
+ end
57
+
58
+ it "options can be specified per-class and are passed to the transport initializer" do
59
+ expect(described_class::DEFAULT_TRANSPORT).to receive(:new).with(anything, hash_including(upper_options))
60
+ A.new.http
61
+ end
62
+
63
+ it "options can be overridden per-class and are passed to the transport initializer" do
64
+ expect(described_class::DEFAULT_TRANSPORT).not_to receive(:new).with(anything, hash_including(upper_options))
65
+ expect(described_class::DEFAULT_TRANSPORT).to receive(:new).with(anything, hash_including(lower_options))
66
+ B.new.http
67
+ end
68
+ end
69
+ end
70
+ end
@@ -4,21 +4,21 @@ shared_examples_for "Songkick::Transport" do
4
4
  before(:all) { TestApp.listen(4567) }
5
5
  after(:all) { TestApp.stop }
6
6
 
7
- describe :get do
7
+ describe "#get" do
8
8
  it "retrieves data using GET" do
9
- transport.get("/artists/99").data.should == {"id" => 99}
9
+ expect(transport.get("/artists/99").data).to eq({"id" => 99})
10
10
  end
11
11
 
12
12
  it "exposes the response headers" do
13
- transport.get("/artists/99").headers["content-type"].should == "application/json"
13
+ expect(transport.get("/artists/99").headers["content-type"]).to eq("application/json")
14
14
  end
15
15
 
16
16
  it "can send array params" do
17
- transport.get("/", :list => %w[a b]).data.should == {"list" => ["a", "b"]}
17
+ expect(transport.get("/", :list => %w[a b]).data).to eq({"list" => ["a", "b"]})
18
18
  end
19
19
 
20
20
  it "can send hash params" do
21
- transport.get("/", :list => {:a => "b"}).data.should == {"list" => {"a" => "b"}}
21
+ expect(transport.get("/", :list => {:a => "b"}).data).to eq({"list" => {"a" => "b"}})
22
22
  end
23
23
 
24
24
  it "can send nested data structures" do
@@ -26,92 +26,89 @@ shared_examples_for "Songkick::Transport" do
26
26
  "hash" => {"a" => {"b" => ["c", "d"], "e" => "f"}},
27
27
  "lisp" => ["define", {"square" => ["x", "y"]}, "*", "x", "x"]
28
28
  }
29
- transport.get("/", structure).data.should == structure
29
+ expect(transport.get("/", structure).data).to eq(structure)
30
30
  end
31
31
 
32
32
  it "raises an UpstreamError for a nonexistent resource" do
33
- lambda { transport.get("/nothing") }.should raise_error(Songkick::Transport::UpstreamError)
33
+ expect { transport.get("/nothing") }.to raise_error(Songkick::Transport::UpstreamError)
34
34
  end
35
35
 
36
36
  it "raises an UpstreamError for a POST resource" do
37
- lambda { transport.get("/artists") }.should raise_error(Songkick::Transport::UpstreamError)
37
+ expect { transport.get("/artists") }.to raise_error(Songkick::Transport::UpstreamError)
38
38
  end
39
39
 
40
40
  it "raises an UpstreamError for invalid JSON" do
41
- lambda { transport.get("/invalid") }.should raise_error(Songkick::Transport::InvalidJSONError)
41
+ expect { transport.get("/invalid") }.to raise_error(Songkick::Transport::InvalidJSONError)
42
42
  end
43
+
43
44
  end
44
45
 
45
- describe :with_headers do
46
+ describe "#with_headers" do
46
47
  it "adds the given headers to requests" do
47
48
  data = transport.with_headers("Authorization" => "correct password").get("/authenticate").data
48
- data.should == {"successful" => true}
49
+ expect(data).to eq({"successful" => true})
49
50
  end
50
51
 
51
52
  it "reformats Rack-style headers" do
52
53
  data = transport.with_headers("HTTP_AUTHORIZATION" => "correct password").get("/authenticate").data
53
- data.should == {"successful" => true}
54
+ expect(data).to eq({"successful" => true})
54
55
  end
55
56
 
56
57
  it "does not affect requests made directly on the transport object" do
57
58
  transport.with_headers("Authorization" => "correct password").get("/authenticate")
58
59
  data = transport.get("/authenticate").data
59
- data.should == {"successful" => false}
60
+ expect(data).to eq({"successful" => false})
60
61
  end
61
62
 
62
63
  it "can set Content-Type" do
63
64
  data = transport.with_headers("Content-Type" => "application/json").post("/content").data
64
- data.should == {"type" => "application/json"}
65
+ expect(data).to eq({"type" => "application/json"})
65
66
  end
66
67
  end
67
68
 
68
- describe :options do
69
+ describe "#options" do
69
70
  it "sends an OPTIONS request" do
70
71
  response = transport.options("/.well-known/host-meta")
71
- response.headers["Access-Control-Allow-Methods"].should == "GET, PUT, DELETE"
72
+ expect(response.headers["Access-Control-Allow-Methods"]).to eq("GET, PUT, DELETE")
72
73
  end
73
74
  end
74
75
 
75
- describe :post do
76
+ describe "#post" do
76
77
  it "sends data using POST" do
77
78
  data = transport.post("/artists", :name => "Amon Tobin").data
78
- data.should == {"id" => "new", "name" => "AMON TOBIN"}
79
+ expect(data).to eq({"id" => "new", "name" => "AMON TOBIN"})
79
80
  end
80
81
 
81
82
  it "can send array params" do
82
- transport.post("/", :list => %w[a b]).data.should == {"list" => ["a", "b"]}
83
+ expect(transport.post("/", :list => %w[a b]).data).to eq({"list" => ["a", "b"]})
83
84
  end
84
85
 
85
86
  it "can send hash params" do
86
- transport.post("/", :list => {:a => "b"}).data.should == {"list" => {"a" => "b"}}
87
+ expect(transport.post("/", :list => {:a => "b"}).data).to eq({"list" => {"a" => "b"}})
87
88
  end
88
89
 
89
90
  it "can send a raw body" do
90
91
  response = transport.with_headers("Content-Type" => "text/plain").post("/process", "Hello, world!")
91
- response.data.should == {"body" => "Hello, world!", "type" => "text/plain"}
92
+ expect(response.data).to eq({"body" => "Hello, world!", "type" => "text/plain"})
92
93
  end
93
94
 
94
95
  it "raises an UpstreamError for a PUT resource" do
95
- lambda { transport.post("/artists/64") }.should raise_error(Songkick::Transport::UpstreamError)
96
+ expect { transport.post("/artists/64") }.to raise_error(Songkick::Transport::UpstreamError)
96
97
  end
97
98
  end
98
99
 
99
- describe :put do
100
+ describe "#put" do
100
101
  it "sends data using PUT" do
101
102
  data = transport.put("/artists/64", :name => "Amon Tobin").data
102
- data.should == {"id" => 64, "name" => "amon tobin"}
103
+ expect(data).to eq({"id" => 64, "name" => "amon tobin"})
103
104
  end
104
105
 
105
106
  it "raises an UpstreamError for a POST resource" do
106
- lambda{transport.put("/artists")}.should raise_error(Songkick::Transport::UpstreamError)
107
+ expect{transport.put("/artists")}.to raise_error(Songkick::Transport::UpstreamError)
107
108
  end
108
109
  end
109
110
 
110
111
  describe "file uploads" do
111
- before do
112
- pending if Songkick::Transport::RackTest === transport
113
- end
114
-
115
112
  after { file.close }
116
113
 
117
114
  let(:file) { File.open(File.expand_path("../../songkick.png", __FILE__)) }
@@ -131,20 +128,78 @@ shared_examples_for "Songkick::Transport" do
131
128
  end
132
129
 
133
130
  it "uploads files using POST" do
134
- @http_method = "post"
135
- response = transport.post('/upload', params)
136
- response.status.should == 200
137
- response.data.should == expected_response
131
+ if Songkick::Transport::RackTest === transport
132
+ else
133
+ @http_method = "post"
134
+ response = transport.post('/upload', params)
135
+ expect(response.status).to eq(200)
136
+ expect(response.data).to eq(expected_response)
137
+ end
138
138
  end
139
139
 
140
140
  it "uploads files using PUT" do
141
- @http_method = "put"
142
- response = transport.put('/upload', params)
143
- response.status.should == 200
144
- response.data.should == expected_response
141
+ if Songkick::Transport::RackTest === transport
142
+ else
143
+ @http_method = "put"
144
+ response = transport.put('/upload', params)
145
+ expect(response.status).to eq(200)
146
+ expect(response.data).to eq(expected_response)
147
+ end
145
148
  end
146
149
  end
147
150
 
151
+ describe 'instrumentation' do
152
+ let(:events) { [] }
153
+ let(:options) { { :instrumenter => ActiveSupport::Notifications } }
154
+
155
+ around do |example|
156
+ label = options[:instrumentation_label] || Songkick::Transport::Base::DEFAULT_INSTRUMENTATION_LABEL
157
+ options[:instrumenter].subscribed(subscriber, label) do
158
+ example.run
159
+ end
160
+ end
161
+
162
+ let(:subscriber) do
163
+ lambda { |*args| events << options[:instrumenter]::Event.new(*args) }
164
+ end
165
+
166
+ context 'for a successful request' do
167
+ let(:path) { '/artists/123' }
168
+
169
+ let(:expected_payload) do
170
+ hash_including(:adapter => /#{described_class.to_s}/,
171
+ :path => path, :endpoint => transport.endpoint,
172
+ :params => {}, :verb => 'get', :status => 200,
173
+ :request_headers => an_instance_of(Hash),
174
+ :response_headers => an_instance_of(Hash))
175
+ end
176
+
177
+
178
+ it 'instruments the request' do
179
+ transport.get(path)
180
+ expect(events.last.payload).to match(expected_payload)
181
+ end
182
+ end
183
+
184
+ context 'for an unsuccessful request' do
185
+ let(:path) { '/nothing' }
186
+
187
+ let(:expected_payload) do
188
+ hash_including(:adapter => /#{described_class.to_s}/,
189
+ :path => path, :endpoint => transport.endpoint,
190
+ :params => {}, :verb => 'post', :status => 404,
191
+ :request_headers => an_instance_of(Hash),
192
+ :response_headers => an_instance_of(Hash))
193
+ end
194
+
195
+ it 'instruments the request' do
196
+ expect { transport.post(path) }.to raise_error(Songkick::Transport::UpstreamError)
197
+ expect(events.last.payload).to match(expected_payload)
198
+ end
199
+ end
200
+
201
+ end
202
+
148
203
  describe "reporting" do
149
204
  before do
150
205
  Songkick::Transport::Reporting.start
@@ -152,36 +207,36 @@ shared_examples_for "Songkick::Transport" do
152
207
  end
153
208
 
154
209
  it "executes a block and returns its value" do
155
- @report.execute { transport.get("/artists/99").data }.should == {"id" => 99}
210
+ expect(@report.execute { transport.get("/artists/99").data }).to eq({"id" => 99})
156
211
  end
157
212
 
158
213
  it "reports a successful request" do
159
214
  @report.execute { transport.get("/artists/99") }
160
- @report.size.should == 1
215
+ expect(@report.size).to eq(1)
161
216
 
162
217
  request = @report.first
163
- request.endpoint.should == endpoint
164
- request.http_method.should == "get"
165
- request.path.should == "/artists/99"
166
- request.response.data.should == {"id" => 99}
218
+ expect(request.endpoint).to eq(endpoint)
219
+ expect(request.http_method).to eq("get")
220
+ expect(request.path).to eq("/artists/99")
221
+ expect(request.response.data).to eq({"id" => 99})
167
222
  end
168
223
 
169
224
  it "reports a failed request" do
170
225
  @report.execute { transport.get("/invalid") } rescue nil
171
- @report.size.should == 1
226
+ expect(@report.size).to eq(1)
172
227
 
173
228
  request = @report.first
174
- request.http_method.should == "get"
175
- request.path.should == "/invalid"
176
- request.response.should == nil
177
- request.error.should be_a(Songkick::Transport::InvalidJSONError)
229
+ expect(request.http_method).to eq("get")
230
+ expect(request.path).to eq("/invalid")
231
+ expect(request.response).to eq(nil)
232
+ expect(request.error).to be_a(Songkick::Transport::InvalidJSONError)
178
233
  end
179
234
 
180
235
  it "reports the total duration" do
181
236
  @report.execute { transport.get("/artists/99") }
182
237
  request = @report.first
183
- request.stub(:duration).and_return 3.14
184
- @report.total_duration.should == 3.14
238
+ allow(request).to receive(:duration).and_return 3.14
239
+ expect(@report.total_duration).to eq(3.14)
185
240
  end
186
241
  end
187
242
 
@@ -190,30 +245,85 @@ shared_examples_for "Songkick::Transport" do
190
245
 
191
246
  it "can be provided on initialization" do
192
247
  transport = described_class.new(endpoint, :user_error_codes => codes)
193
- transport.user_error_codes.should == codes
248
+ expect(transport.user_error_codes).to eq(codes)
194
249
  end
195
250
 
196
251
  it "default to 409" do
197
- transport.user_error_codes.should == [409]
252
+ expect(transport.user_error_codes).to eq([409])
198
253
  end
199
254
  end
255
+
200
256
  end
201
257
 
202
- describe Songkick::Transport::Curb do
203
- let(:endpoint) { "http://localhost:4567" }
204
- let(:transport) { Songkick::Transport::Curb.new(endpoint) }
205
- it_should_behave_like "Songkick::Transport"
258
+ # Curb always times out talking to the web server in the other thread when run on 1.8
259
+ unless RUBY_VERSION =~ /^1.8/
260
+ describe Songkick::Transport::Curb do
261
+ let(:options) { {} }
262
+ let(:endpoint) { "http://localhost:4567" }
263
+ let(:transport) { described_class.new(endpoint, options) }
264
+ it_should_behave_like "Songkick::Transport"
265
+ end
206
266
  end
207
267
 
208
268
  describe Songkick::Transport::HttParty do
269
+ let(:options) { {} }
209
270
  let(:endpoint) { "http://localhost:4567" }
210
- let(:transport) { Songkick::Transport::HttParty.new(endpoint) }
271
+ let(:transport) { described_class.new(endpoint, options) }
211
272
  it_should_behave_like "Songkick::Transport"
212
273
  end
213
274
 
214
275
  describe Songkick::Transport::RackTest do
276
+ let(:options) { {} }
215
277
  let(:endpoint) { TestApp }
216
- let(:transport) { Songkick::Transport::RackTest.new(endpoint) }
278
+ let(:transport) { described_class.new(endpoint, options) }
217
279
  it_should_behave_like "Songkick::Transport"
218
280
  end
219
281
 
282
+ describe Songkick::Transport do
283
+ describe "registering parsers" do
284
+ context "for JSON" do
285
+ it 'should use the JSON parser' do
286
+ expect(described_class.parser_for('application/json')).to eq(Yajl::Parser)
287
+ end
288
+ end
289
+
290
+ context "for anything else" do
291
+ context 'when there is no registered parser' do
292
+ it 'should raise an error' do
293
+ expect { described_class.parser_for('application/xml') }.to raise_error(TypeError)
294
+ end
295
+ end
296
+
297
+ context 'when there is a registered parser' do
298
+ before do
299
+ described_class.register_parser('application/xml', :my_parser)
300
+ end
301
+
302
+ it 'should return that parser' do
303
+ expect(described_class.parser_for('application/xml')).to eq(:my_parser)
304
+ end
305
+
306
+ context 'when there is no registered parser' do
307
+ it 'should raise an error' do
308
+ expect { described_class.parser_for('application/x-something-else') }.to raise_error(TypeError)
309
+ end
310
+ end
311
+
312
+ context 'when there is a default registered parser' do
313
+ before do
314
+ described_class.register_default_parser(:my_default_parser)
315
+ end
316
+
317
+ it 'should return that parser' do
318
+ expect(described_class.parser_for('application/x-something-else')).to eq(:my_default_parser)
319
+ end
320
+
321
+ it 'should return the already defined parser if specified' do
322
+ expect(described_class.parser_for('application/xml')).to eq(:my_parser)
323
+ expect(described_class.parser_for('application/json')).to eq(Yajl::Parser)
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end