nervion 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +46 -0
- data/LICENSE +22 -0
- data/README.md +197 -0
- data/Rakefile +22 -0
- data/features/step_definitions/streaming_steps.rb +55 -0
- data/features/streaming.feature +16 -0
- data/features/support/env.rb +9 -0
- data/features/support/streaming_api_double.rb +45 -0
- data/fixtures/responses.rb +53 -0
- data/fixtures/stream.txt +100 -0
- data/lib/nervion.rb +4 -0
- data/lib/nervion/callback_table.rb +30 -0
- data/lib/nervion/client.rb +23 -0
- data/lib/nervion/configuration.rb +50 -0
- data/lib/nervion/facade.rb +54 -0
- data/lib/nervion/http_parser.rb +62 -0
- data/lib/nervion/oauth_header.rb +88 -0
- data/lib/nervion/oauth_signature.rb +54 -0
- data/lib/nervion/percent_encoder.rb +11 -0
- data/lib/nervion/reconnection_scheduler.rb +96 -0
- data/lib/nervion/request.rb +99 -0
- data/lib/nervion/stream.rb +64 -0
- data/lib/nervion/stream_handler.rb +42 -0
- data/lib/nervion/version.rb +3 -0
- data/nervion.gemspec +24 -0
- data/spec/nervion/callback_table_spec.rb +18 -0
- data/spec/nervion/client_spec.rb +51 -0
- data/spec/nervion/configuration_spec.rb +58 -0
- data/spec/nervion/facade_spec.rb +90 -0
- data/spec/nervion/http_parser_spec.rb +26 -0
- data/spec/nervion/oauth_header_spec.rb +115 -0
- data/spec/nervion/oauth_signature_spec.rb +66 -0
- data/spec/nervion/percent_encoder_spec.rb +20 -0
- data/spec/nervion/reconnection_scheduler_spec.rb +84 -0
- data/spec/nervion/request_spec.rb +90 -0
- data/spec/nervion/stream_handler_spec.rb +67 -0
- data/spec/nervion/stream_spec.rb +97 -0
- data/spec/spec_helper.rb +4 -0
- metadata +200 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'nervion/facade'
|
2
|
+
|
3
|
+
describe "Facade that exposes Nervion's API" do
|
4
|
+
let(:callback_table) { mock(:callback_table).as_null_object }
|
5
|
+
let(:status_callback) { lambda { :status_callback } }
|
6
|
+
let(:http_callback) { lambda { :http_error_callback } }
|
7
|
+
let(:network_callback) { lambda { :network_error_callback } }
|
8
|
+
|
9
|
+
before { Nervion.stub(:callback_table).and_return(callback_table) }
|
10
|
+
|
11
|
+
it 'provides a call to set up the http error callback' do
|
12
|
+
callback_table.should_receive(:[]=).with(:http_error, http_callback)
|
13
|
+
Nervion.on_http_error(&http_callback)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'provides a call to set up the network error callback' do
|
17
|
+
callback_table.should_receive(:[]=).with(:network_error, network_callback)
|
18
|
+
Nervion.on_network_error(&network_callback)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'chaining callback setup calls' do
|
22
|
+
before do
|
23
|
+
callback_table.should_receive(:[]=).with(:http_error, http_callback)
|
24
|
+
callback_table.should_receive(:[]=).with(:network_error, network_callback)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'allows to chain callback setups' do
|
28
|
+
Nervion.on_http_error(&http_callback).on_network_error(&network_callback)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'callback setups can be chained in any order' do
|
32
|
+
Nervion.on_network_error(&network_callback).on_http_error(&http_callback)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'streaming' do
|
37
|
+
let(:client) { stub(:client).as_null_object }
|
38
|
+
let(:config) { Nervion::Configuration }
|
39
|
+
let(:params) { Hash[stall_warnings: true] }
|
40
|
+
let(:request) { stub :request }
|
41
|
+
|
42
|
+
before do
|
43
|
+
Nervion::Client.stub(:new).
|
44
|
+
with(Nervion::STREAM_API_HOST, Nervion::STREAM_API_PORT).
|
45
|
+
and_return(client)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'sample endpoint' do
|
49
|
+
it 'sets up the status callback' do
|
50
|
+
callback_table.should_receive(:[]=).with(:status, status_callback)
|
51
|
+
Nervion.sample(&status_callback)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'starts the streaming to the sample endpoint' do
|
55
|
+
Nervion.stub(:get).with(Nervion::SAMPLE_ENDPOINT, params, config).
|
56
|
+
and_return(request)
|
57
|
+
client.should_receive(:stream).with(request, callback_table)
|
58
|
+
Nervion.sample(params, &status_callback)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'filter endpoint' do
|
63
|
+
it 'sets up the status callback' do
|
64
|
+
callback_table.should_receive(:[]=).with(:status, status_callback)
|
65
|
+
Nervion.filter(params, &status_callback)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'starts the streaming to the filter endpoint' do
|
69
|
+
Nervion.stub(:post).with(Nervion::FILTER_ENDPOINT, params, config).
|
70
|
+
and_return(request)
|
71
|
+
client.should_receive(:stream).with(request, callback_table)
|
72
|
+
Nervion.filter(params, &status_callback)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'stoping' do
|
77
|
+
it 'can stop the streaming' do
|
78
|
+
client.should_receive(:stop)
|
79
|
+
Nervion.sample(->{})
|
80
|
+
Nervion.stop
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'raises an error if it is not streaming' do
|
84
|
+
Nervion.instance_variable_set(:@client, nil)
|
85
|
+
expect { Nervion.stop }.to raise_error
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'nervion/http_parser'
|
2
|
+
require 'fixtures/responses'
|
3
|
+
|
4
|
+
describe Nervion::HttpParser do
|
5
|
+
subject { described_class.new(json_parser) }
|
6
|
+
let(:json_parser) { stub(:json_parser).as_null_object }
|
7
|
+
|
8
|
+
it 'takes a JSON parser' do
|
9
|
+
subject.json_parser.should be json_parser
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be reset' do
|
13
|
+
subject << RESPONSE_200
|
14
|
+
subject.reset!
|
15
|
+
expect { subject << RESPONSE_200 }.not_to raise_error Http::Parser::Error
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'parses response body if the response status is 200' do
|
19
|
+
json_parser.should_receive(:<<).with(BODY_200)
|
20
|
+
subject << RESPONSE_200
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises an error if the response status is above 200' do
|
24
|
+
expect { subject << RESPONSE_401 }.to raise_error Nervion::HttpError
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'nervion/oauth_header'
|
2
|
+
|
3
|
+
describe Nervion::OAuthHeader do
|
4
|
+
subject { described_class.new http_method, base_url, params, oauth_params }
|
5
|
+
|
6
|
+
let(:http_method) { 'post' }
|
7
|
+
let(:base_url) { 'https://api.twitter.com/1/statuses/update.json' }
|
8
|
+
let(:params) { Hash[include_entities: true, status: '@patheleven'] }
|
9
|
+
let(:consumer_key) { 'xvz1evFS4wEEPTGEFPHBog' }
|
10
|
+
let(:consumer_secret) { 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw' }
|
11
|
+
let(:access_token) { 'GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb' }
|
12
|
+
let(:access_token_secret) { 'LswwdoUaIvS8ltyTth4J50vUPVVHtR2YPi5kE' }
|
13
|
+
let(:oauth_params) do
|
14
|
+
{
|
15
|
+
consumer_key: consumer_key,
|
16
|
+
consumer_secret: consumer_secret,
|
17
|
+
access_token: access_token,
|
18
|
+
access_token_secret: access_token_secret
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'takes the consumer key from oauth params' do
|
23
|
+
subject.consumer_key.should be consumer_key
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'takes the consumer secret from oauth params' do
|
27
|
+
subject.consumer_secret.should be consumer_secret
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'takes the access token from oauth params' do
|
31
|
+
subject.token.should be access_token
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'takes the access token secret from oauth params' do
|
35
|
+
subject.token_secret.should be access_token_secret
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'nonce is md5 of a string formed by consumer key, token and timestamp' do
|
39
|
+
nonce, timestamp = stub, 123456878
|
40
|
+
subject.stub(:timestamp).and_return timestamp
|
41
|
+
string = "#{consumer_key}#{access_token}#{timestamp}"
|
42
|
+
Digest::MD5.stub(:hexdigest).with(string).and_return nonce
|
43
|
+
subject.nonce.should be nonce
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'timestamp is seconds since epoch as string' do
|
47
|
+
seconds_since_epoch = stub(to_s: 'timestamp')
|
48
|
+
now = stub(to_i: seconds_since_epoch)
|
49
|
+
Time.stub(:now).and_return now
|
50
|
+
subject.timestamp.should eq 'timestamp'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'signature method is HMAC-SHA1' do
|
54
|
+
subject.signature_method.should eq 'HMAC-SHA1'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'version is 1.0' do
|
58
|
+
subject.version.should eq '1.0'
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'provides a hash with the info required to create a signature' do
|
62
|
+
nonce, timestamp = stub(:nonce), stub(:timestamp)
|
63
|
+
subject.stub(:nonce).and_return nonce
|
64
|
+
subject.stub(:timestamp).and_return timestamp
|
65
|
+
oauth_info = {
|
66
|
+
oauth_consumer_key: consumer_key,
|
67
|
+
oauth_nonce: nonce,
|
68
|
+
oauth_signature_method: 'HMAC-SHA1',
|
69
|
+
oauth_timestamp: timestamp,
|
70
|
+
oauth_token: access_token,
|
71
|
+
oauth_version: '1.0'
|
72
|
+
}
|
73
|
+
subject.oauth_info.should eq oauth_info
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'provides a hash with the consumer secret and token secret' do
|
77
|
+
expected_secret_hash = {
|
78
|
+
consumer_secret: consumer_secret,
|
79
|
+
access_token_secret: access_token_secret
|
80
|
+
}
|
81
|
+
subject.secrets.should eq expected_secret_hash
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'creates the signature' do
|
85
|
+
signature, oauth_info = stub(:signature), stub(:oauth_info)
|
86
|
+
subject.stub(:oauth_info).and_return(oauth_info)
|
87
|
+
secrets = {
|
88
|
+
consumer_secret: consumer_secret,
|
89
|
+
access_token_secret: access_token_secret
|
90
|
+
}
|
91
|
+
Nervion::OAuthSignature.stub(:for).
|
92
|
+
with(http_method, base_url, params, oauth_info, secrets).
|
93
|
+
and_return signature
|
94
|
+
subject.signature.should be signature
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'generates the authorization header value' do
|
98
|
+
subject.stub(:nonce).and_return 'nonce'
|
99
|
+
subject.stub(:timestamp).and_return 'timestamp'
|
100
|
+
subject.stub(:signature).and_return 'signature'
|
101
|
+
|
102
|
+
expected_header = %Q{OAuth oauth_consumer_key="#{consumer_key}", oauth_nonce="nonce", oauth_signature="signature", oauth_signature_method="HMAC-SHA1", oauth_timestamp="timestamp", oauth_token="#{access_token}", oauth_version="1.0"}
|
103
|
+
|
104
|
+
subject.to_s.should eq expected_header
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'generates the header value for a paticular request' do
|
108
|
+
oauth_headers = stub :oauth_headers, to_s: 'OAuth oauth_params="value"'
|
109
|
+
Nervion::OAuthHeader.stub(:new).
|
110
|
+
with(http_method, base_url, params, oauth_params).and_return oauth_headers
|
111
|
+
request = stub :request, http_method: http_method, uri: base_url,
|
112
|
+
params: params, oauth_params: oauth_params
|
113
|
+
described_class.for(request).should eq 'OAuth oauth_params="value"'
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'nervion/oauth_signature'
|
2
|
+
|
3
|
+
describe Nervion::OAuthSignature do
|
4
|
+
subject { described_class.new http_method, base_url, params, oauth_params, secrets }
|
5
|
+
|
6
|
+
let(:http_method) { 'post' }
|
7
|
+
let(:base_url) { 'https://api.twitter.com/1/statuses/update.json' }
|
8
|
+
let(:params) do
|
9
|
+
{
|
10
|
+
include_entities: true,
|
11
|
+
status: 'Hello Ladies + Gentlemen, a signed OAuth request!'
|
12
|
+
}
|
13
|
+
end
|
14
|
+
let(:oauth_params) do
|
15
|
+
{
|
16
|
+
oauth_consumer_key: 'xvz1evFS4wEEPTGEFPHBog',
|
17
|
+
oauth_nonce: 'kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg',
|
18
|
+
oauth_signature_method: 'HMAC-SHA1',
|
19
|
+
oauth_timestamp: '1318622958',
|
20
|
+
oauth_token: '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb',
|
21
|
+
oauth_version: '1.0',
|
22
|
+
}
|
23
|
+
end
|
24
|
+
let(:secrets) do
|
25
|
+
{
|
26
|
+
consumer_secret: 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw',
|
27
|
+
access_token_secret: 'LswwdoUaIvS8ltyTth4J50vUPVVHtR2YPi5kE'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'percent encodes key and value and joins the result with a "="' do
|
32
|
+
subject.stub(:encode).with('include_entities').and_return 'include_entities'
|
33
|
+
subject.stub(:encode).with('true').and_return 'true'
|
34
|
+
subject.encode_pair(:include_entities, true).should eq 'include_entities=true'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'provides the parameter string' do
|
38
|
+
expected_parameter_string = %q{include_entities=true&oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1318622958&oauth_token=370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb&oauth_version=1.0&status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21}
|
39
|
+
|
40
|
+
subject.parameter_string.should eq expected_parameter_string
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'provides the signature base string' do
|
44
|
+
expected_signature_base_string = %q{POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521}
|
45
|
+
|
46
|
+
subject.base_string.should eq expected_signature_base_string
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'provides a signing key' do
|
50
|
+
expected_signing_key = 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTth4J50vUPVVHtR2YPi5kE'
|
51
|
+
|
52
|
+
subject.signing_key.should eq expected_signing_key
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'calculates the signature' do
|
56
|
+
expected_signature = 'Fz/2gWGHnXm6+QRzVUtANvhr1wI='
|
57
|
+
|
58
|
+
subject.to_s.should eq expected_signature
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'builds the signature given all the info' do
|
62
|
+
expected_signature = 'Fz/2gWGHnXm6+QRzVUtANvhr1wI='
|
63
|
+
signature = described_class.for http_method, base_url, params, oauth_params, secrets
|
64
|
+
signature.should eq expected_signature
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'nervion/percent_encoder'
|
4
|
+
|
5
|
+
describe Nervion::PercentEncoder do
|
6
|
+
it 'encodes string values' do
|
7
|
+
subject.encode('Ladies + Gentlemen').should eq 'Ladies%20%2B%20Gentlemen'
|
8
|
+
subject.encode('An encoded string!').should eq 'An%20encoded%20string%21'
|
9
|
+
subject.encode('Dogs, Cats & Mice').should eq 'Dogs%2C%20Cats%20%26%20Mice'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'encodes non string values' do
|
13
|
+
subject.encode(123456789).should eq '123456789'
|
14
|
+
subject.encode(:get_post).should eq 'get_post'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'encodes UTF-8 values' do
|
18
|
+
subject.encode('☃').should eq '%E2%98%83'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'nervion/reconnection_scheduler'
|
2
|
+
|
3
|
+
describe Nervion::ReconnectionScheduler do
|
4
|
+
let(:stream) { stub(:connection).as_null_object }
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
module EventMachine
|
8
|
+
class << self
|
9
|
+
alias old_add_timer add_timer
|
10
|
+
def add_timer(timeout); yield; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:all) do
|
16
|
+
module EventMachine
|
17
|
+
class << self
|
18
|
+
alias add_timer old_add_timer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'on HTTP errors' do
|
24
|
+
it 'tells the stream to reconnect' do
|
25
|
+
stream.should_receive(:retry)
|
26
|
+
subject.reconnect_after_http_error_in stream
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'waits 10 seconds before reconnecting' do
|
30
|
+
EM.should_receive(:add_timer).with Nervion::HttpWaitCalculator::MIN_WAIT
|
31
|
+
subject.reconnect_after_http_error_in stream
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'increases the reconnect wait exponentially up to 240 seconds' do
|
35
|
+
[10, 20, 40, 80, 160, 240, 240, 240, 240].each do |delay|
|
36
|
+
EM.should_receive(:add_timer).with delay
|
37
|
+
subject.reconnect_after_http_error_in stream
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises an error after too many unsuccessful reconnects' do
|
42
|
+
limit = described_class::HTTP_ERROR_LIMIT + 1
|
43
|
+
expect do
|
44
|
+
limit.times { subject.reconnect_after_http_error_in stream }
|
45
|
+
end.to raise_error Nervion::TooManyConnectionErrors
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'on network errors' do
|
50
|
+
it 'tells the stream to reconnect' do
|
51
|
+
stream.should_receive(:retry)
|
52
|
+
subject.reconnect_after_network_error_in stream
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'waits 250ms before reconnecting' do
|
56
|
+
EM.should_receive(:add_timer).with Nervion::NetworkWaitCalculator::MIN_WAIT
|
57
|
+
subject.reconnect_after_network_error_in stream
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'increases the wait after network errors linearly up to 16 seconds' do
|
61
|
+
(0.25..16).step(0.25) do |wait|
|
62
|
+
EM.should_receive(:add_timer).with wait
|
63
|
+
subject.reconnect_after_network_error_in stream
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'caps the wait after network errors at 16 seconds' do
|
68
|
+
errors_to_cap = (16/0.25).to_i - 1
|
69
|
+
errors_to_limit = described_class::NETWORK_ERROR_LIMIT - errors_to_cap - 1
|
70
|
+
errors_to_cap.times { subject.reconnect_after_network_error_in stream }
|
71
|
+
errors_to_limit.times do
|
72
|
+
EM.should_receive(:add_timer).with(16)
|
73
|
+
subject.reconnect_after_network_error_in stream
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'raises an error after too many unsuccessful reconnects' do
|
78
|
+
limit = described_class::NETWORK_ERROR_LIMIT + 1
|
79
|
+
expect do
|
80
|
+
limit.times { subject.reconnect_after_network_error_in stream }
|
81
|
+
end.to raise_error Nervion::TooManyConnectionErrors
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'nervion/request'
|
2
|
+
|
3
|
+
EXPECTED_GET_REQUEST = <<GET
|
4
|
+
GET /endpoint?p1=param%20value&p2=%24%26 HTTP/1.1\r
|
5
|
+
Host: twitter.com\r
|
6
|
+
Authorization: OAuth xxx\r\n\r
|
7
|
+
GET
|
8
|
+
|
9
|
+
EXPECTED_POST_REQUEST = <<POST
|
10
|
+
POST /endpoint HTTP/1.1\r
|
11
|
+
Host: twitter.com\r
|
12
|
+
Authorization: OAuth xxx\r
|
13
|
+
Content-Type: application/x-www-form-urlencoded\r
|
14
|
+
Content-Length: 26\r
|
15
|
+
\r
|
16
|
+
p1=param%20value&p2=%24%26\r
|
17
|
+
POST
|
18
|
+
|
19
|
+
describe Nervion::Request do
|
20
|
+
let(:uri) { 'https://twitter.com:443/endpoint' }
|
21
|
+
let(:params) { Hash[p1: 'param value', p2: '$&'] }
|
22
|
+
let(:oauth_params) { Hash[param: 'value'] }
|
23
|
+
|
24
|
+
shared_examples_for 'a request' do
|
25
|
+
it 'is created with an uri' do
|
26
|
+
subject.uri.should eq 'https://twitter.com/endpoint'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is created with http params' do
|
30
|
+
subject.params.should be params
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'is created with oauth params' do
|
34
|
+
subject.oauth_params.should eq Hash[param: 'value']
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'knows the host it points to' do
|
38
|
+
subject.host.should eq 'twitter.com'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'knows the port it will connect to' do
|
42
|
+
subject.port.should eq 443
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'GET' do
|
47
|
+
subject { Nervion.get(uri, params, oauth_params) }
|
48
|
+
|
49
|
+
it 'has GET as http method' do
|
50
|
+
subject.http_method.should eq 'GET'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'knows the path it points to with no params' do
|
54
|
+
get_with_no_params = Nervion.get(uri, {}, oauth_params)
|
55
|
+
get_with_no_params.path.should eq '/endpoint'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'knows the path it points to with params' do
|
59
|
+
subject.path.should eq '/endpoint?p1=param%20value&p2=%24%26'
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
it 'has an string representation' do
|
64
|
+
Nervion::OAuthHeader.stub(:for).with(subject).and_return 'OAuth xxx'
|
65
|
+
subject.to_s.should eq EXPECTED_GET_REQUEST
|
66
|
+
end
|
67
|
+
|
68
|
+
it_behaves_like 'a request'
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'POST' do
|
72
|
+
subject { Nervion.post(uri, params, oauth_params) }
|
73
|
+
|
74
|
+
it 'has POST as http method' do
|
75
|
+
subject.http_method.should eq 'POST'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'knows the path it points to' do
|
79
|
+
subject.path.should eq '/endpoint'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'has an string representation' do
|
83
|
+
post = Nervion.post(uri, params, oauth_params)
|
84
|
+
Nervion::OAuthHeader.stub(:for).with(post).and_return 'OAuth xxx'
|
85
|
+
post.to_s.should eq EXPECTED_POST_REQUEST
|
86
|
+
end
|
87
|
+
|
88
|
+
it_behaves_like 'a request'
|
89
|
+
end
|
90
|
+
end
|