nervion 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|