angus-remote 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/angus-remote.rb +4 -0
- data/lib/angus/remote/builder.rb +204 -0
- data/lib/angus/remote/client.rb +79 -0
- data/lib/angus/remote/exceptions.rb +50 -0
- data/lib/angus/remote/http/multipart.rb +54 -0
- data/lib/angus/remote/http/multipart_methods/multipart_base.rb +36 -0
- data/lib/angus/remote/http/multipart_methods/multipart_post.rb +11 -0
- data/lib/angus/remote/http/multipart_methods/multipart_put.rb +11 -0
- data/lib/angus/remote/http/query_params.rb +53 -0
- data/lib/angus/remote/message.rb +14 -0
- data/lib/angus/remote/proxy_client.rb +58 -0
- data/lib/angus/remote/proxy_client_utils.rb +70 -0
- data/lib/angus/remote/remote_response.rb +44 -0
- data/lib/angus/remote/representation.rb +18 -0
- data/lib/angus/remote/response/builder.rb +308 -0
- data/lib/angus/remote/response/hash.rb +47 -0
- data/lib/angus/remote/response/serializer.rb +43 -0
- data/lib/angus/remote/service_directory.rb +217 -0
- data/lib/angus/remote/utils.rb +119 -0
- data/lib/angus/remote/version.rb +4 -2
- data/lib/angus/unmarshalling.rb +33 -0
- data/spec/angus/remote/builder_spec.rb +105 -0
- data/spec/angus/remote/client_spec.rb +75 -0
- data/spec/angus/remote/http/multipart_methods/multipart_base_spec.rb +36 -0
- data/spec/angus/remote/http/multipart_spec.rb +120 -0
- data/spec/angus/remote/http/query_params_spec.rb +28 -0
- data/spec/angus/remote/proxy_client_utils_spec.rb +102 -0
- data/spec/angus/remote/response/builder_spec.rb +69 -0
- data/spec/angus/remote/service_directory_spec.rb +76 -0
- data/spec/angus/remote/utils_spec.rb +204 -0
- metadata +192 -32
- data/.gitignore +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -22
- data/README.md +0 -29
- data/Rakefile +0 -1
- data/angus-remote.gemspec +0 -23
- data/lib/angus/remote.rb +0 -7
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'angus/remote/client'
|
4
|
+
require 'angus/remote/builder'
|
5
|
+
|
6
|
+
describe Angus::Remote::Builder do
|
7
|
+
|
8
|
+
subject(:builder) { Angus::Remote::Builder }
|
9
|
+
|
10
|
+
describe '.build' do
|
11
|
+
|
12
|
+
let(:code_name) { 'vpos' }
|
13
|
+
let(:operation) { double(:operation, :code_name=> 'get_users', :service_name => 'vpos', :path => '/users', :method => :get) }
|
14
|
+
let(:proxy_operation) { double(:proxy_operation, :code_name=> 'get_users_proxy', :service_name => 'vpos', :path => '/users', :method => :get) }
|
15
|
+
let(:glossary) { double(:glossary, :terms_hash_with_long_names => {}) }
|
16
|
+
let(:service_definition) { double(:vpos, :name => 'Vpos', :operations => { 'users' => [operation] },
|
17
|
+
:proxy_operations => { 'users' => [proxy_operation] }, :version => '0.1', :glossary => glossary) }
|
18
|
+
|
19
|
+
let(:api_url) { 'http://localhost:8085/vpos/api/0.1/' }
|
20
|
+
|
21
|
+
describe 'the returned class' do
|
22
|
+
|
23
|
+
subject(:client) { builder.build(code_name, service_definition, api_url) }
|
24
|
+
|
25
|
+
it 'is of class Angus::Remote::Client' do
|
26
|
+
should be_kind_of(Angus::Remote::Client)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'responds to the defined operation' do
|
30
|
+
should respond_to(:get_users)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'the generated operation' do
|
34
|
+
|
35
|
+
let(:response) { double(:response, :code => 200, :body => JSON({ :status => 'success' })) }
|
36
|
+
|
37
|
+
let(:service_configuration) {
|
38
|
+
{
|
39
|
+
'v0.1' => { 'doc_url' => 'some_url/doc', 'api_url' => 'some_url/api' }
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
let(:service_def) {
|
44
|
+
{
|
45
|
+
'service' => { 'service' => 'vpos' }, 'code_name' => 'vpos', 'version' => '0.1',
|
46
|
+
'operations' => { 'users' => { 'get_users' => { 'name' => 'Obtener usuarios'} } }
|
47
|
+
}
|
48
|
+
|
49
|
+
}
|
50
|
+
|
51
|
+
before do
|
52
|
+
Angus::Remote::ServiceDirectory.stub(:service_configuration => service_configuration)
|
53
|
+
Angus::Remote::ServiceDirectory.stub(:fetch_remote_service_definition => service_def)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'makes a request to the remote service' do
|
57
|
+
client.should_receive(:make_request).and_return(response)
|
58
|
+
|
59
|
+
client.get_users
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'the generated proxy operation' do
|
63
|
+
|
64
|
+
before do
|
65
|
+
Angus::Remote::ServiceDirectory.stub(:service_configuration => { 'v0.1' => { 'doc_url' => 'some_url/doc', 'api_url' => 'some_url/api' } })
|
66
|
+
Angus::Remote::ServiceDirectory.stub(:fetch_remote_service_definition => { 'service' => { 'service' => 'vpos' }, 'code_name' => 'vpos', 'version' => '0.1', 'operations' => { 'users' => { 'get_users_proxy' => { 'name' => 'Obtener usuarios' } } } })
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'makes a request to the remote service' do
|
70
|
+
client.should_receive(:make_request).and_return(response)
|
71
|
+
|
72
|
+
client.get_users_proxy
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.build_client_class' do
|
84
|
+
|
85
|
+
let(:name) { 'Foo' }
|
86
|
+
let(:url) { 'http://bar' }
|
87
|
+
|
88
|
+
it 'returns a client class' do
|
89
|
+
client_class = builder.build_client_class(name)
|
90
|
+
|
91
|
+
client = client_class.new(url)
|
92
|
+
|
93
|
+
client.should be_kind_of(Angus::Remote::Client)
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'the returned class' do
|
97
|
+
subject { builder.build_client_class(name) }
|
98
|
+
|
99
|
+
its(:name) { should include(name) }
|
100
|
+
its(:to_s) { should include(name) }
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'angus/remote/client'
|
4
|
+
|
5
|
+
describe Angus::Remote::Client do
|
6
|
+
|
7
|
+
let(:url) { 'http://bar' }
|
8
|
+
subject(:client) { Angus::Remote::Client.new(url) }
|
9
|
+
|
10
|
+
describe '#to_s' do
|
11
|
+
|
12
|
+
it 'returns the class name and the object id' do
|
13
|
+
client.to_s.should eq("#<#{client.class}:#{client.object_id}>")
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#make_request' do
|
19
|
+
|
20
|
+
it 'returns the remote service response' do
|
21
|
+
response = double(:response, :code => 200, :body => '[]')
|
22
|
+
PersistentHTTP.any_instance.stub(:request => response)
|
23
|
+
|
24
|
+
client.make_request('/users', 'get', false, [], {}).should eq(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when an invalid method is used' do
|
28
|
+
it 'raises MethodArgumentError' do
|
29
|
+
expect {
|
30
|
+
client.make_request('/', 'INVALID_METHOD', false, [], {})
|
31
|
+
}.to raise_error(Angus::Remote::MethodArgumentError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when less path_params that expected' do
|
36
|
+
it 'raises PathArgumentError' do
|
37
|
+
expect {
|
38
|
+
client.make_request('/a/:b/c/:d', 'get', false, [], {})
|
39
|
+
}.to raise_error(Angus::Remote::PathArgumentError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when more path_params that expected' do
|
44
|
+
it 'raises PathArgumentError' do
|
45
|
+
expect {
|
46
|
+
client.make_request('/a/:b/c/:d', 'get', false, [1, 2, 3], {})
|
47
|
+
}.to raise_error(Angus::Remote::PathArgumentError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when the remote service returns a severe error response' do
|
52
|
+
let(:error_response) { double(:error_response, :code => 500, :body => '') }
|
53
|
+
|
54
|
+
before { PersistentHTTP.any_instance.stub(:request => error_response) }
|
55
|
+
|
56
|
+
it 'raises RemoteSevereError' do
|
57
|
+
expect {
|
58
|
+
client.make_request('/users', 'get', false, [], {})
|
59
|
+
}.to raise_error(Angus::Remote::RemoteSevereError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when the remote service rejects the connection' do
|
64
|
+
before { PersistentHTTP.any_instance.stub(:request).and_raise(Errno::ECONNREFUSED) }
|
65
|
+
|
66
|
+
it 'raises RemoteConnectionError' do
|
67
|
+
expect {
|
68
|
+
client.make_request('/users', 'get', false, [], {})
|
69
|
+
}.to raise_error(Angus::Remote::RemoteConnectionError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'angus/remote/http/multipart_methods/multipart_base'
|
4
|
+
|
5
|
+
describe Http::MultipartMethods::MultipartBase do
|
6
|
+
|
7
|
+
let :request_class do
|
8
|
+
Class.new(Net::HTTP::Post) do
|
9
|
+
include Http::MultipartMethods::MultipartBase
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'headers set by .body= are retained if .initialize_http_header is called afterwards' do
|
14
|
+
def request_with_headers(headers)
|
15
|
+
request_class.new('/path').tap do |request|
|
16
|
+
request.body = { :some => :var }
|
17
|
+
request.initialize_http_header(headers)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a header' do
|
22
|
+
subject { request_with_headers({'a' => 'header'}).to_hash }
|
23
|
+
|
24
|
+
it { should include('content-length') }
|
25
|
+
it { should include('a') }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'without a header' do
|
29
|
+
subject { request_with_headers(nil).to_hash }
|
30
|
+
|
31
|
+
it { should include('content-length') }
|
32
|
+
it { should_not include('a') }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'fakefs/spec_helpers'
|
4
|
+
|
5
|
+
require 'angus/remote/http/multipart'
|
6
|
+
|
7
|
+
describe Http::Multipart do
|
8
|
+
|
9
|
+
include FakeFS::SpecHelpers
|
10
|
+
|
11
|
+
let(:file_name) { 'some_file.txt' }
|
12
|
+
|
13
|
+
let(:file) { File.new(file_name) }
|
14
|
+
let(:tempfile) { Tempfile.new('tempfile', temp_dir) }
|
15
|
+
let(:temp_dir) { '/tmp' }
|
16
|
+
let(:some_upload_io) { UploadIO.new(file, 'application/octet-stream') }
|
17
|
+
|
18
|
+
before do
|
19
|
+
Dir.mkdir('/tmp')
|
20
|
+
|
21
|
+
File.new(file_name, 'w')
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.hash_contains_files?' do
|
25
|
+
|
26
|
+
it 'should return true if one of the values in the passed hash is a file' do
|
27
|
+
Http::Multipart.hash_contains_files?({:a => 1, :file => file}).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should return true if one of the values in the passed hash is an upload io' do
|
31
|
+
Http::Multipart.hash_contains_files?({:a => 1, :file => some_upload_io}).should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should return true if one of the values in the passed hash is a tempfile' do
|
35
|
+
Http::Multipart.hash_contains_files?({:a => 1, :file => tempfile}).should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should return false if none of the values in the passed hash is a file' do
|
39
|
+
Http::Multipart.hash_contains_files?({:a => 1, :b => 'nope'}).should be_false
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should return true if passed hash includes an a array of files' do
|
43
|
+
Http::Multipart.hash_contains_files?({:files => [file, file]}).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
describe '.file_to_upload_io' do
|
50
|
+
|
51
|
+
it 'should get the physical name of a file' do
|
52
|
+
Http::Multipart.file_to_upload_io(file).original_filename.should == file_name
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should get the physical name of a file' do
|
56
|
+
# Let's pretend this is a file upload to a rack app.
|
57
|
+
tempfile.stub(:original_filename => 'stuff.txt')
|
58
|
+
|
59
|
+
Http::Multipart.file_to_upload_io(tempfile).original_filename.should == 'stuff.txt'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '.flatten_params' do
|
65
|
+
|
66
|
+
it 'should handle complex hashs' do
|
67
|
+
Http::Multipart.flatten_params({
|
68
|
+
:foo => 'bar',
|
69
|
+
:deep => {
|
70
|
+
:deeper => 1,
|
71
|
+
:deeper2 => 2,
|
72
|
+
:deeparray => [1,2,3],
|
73
|
+
:deephasharray => [
|
74
|
+
{:id => 1},
|
75
|
+
{:id => 2}
|
76
|
+
]
|
77
|
+
}
|
78
|
+
}).sort_by(&:join).should == [
|
79
|
+
['foo', 'bar'],
|
80
|
+
['deep[deeper]', 1],
|
81
|
+
['deep[deeper2]', 2],
|
82
|
+
['deep[deeparray][]', 1],
|
83
|
+
['deep[deeparray][]', 2],
|
84
|
+
['deep[deeparray][]', 3],
|
85
|
+
['deep[deephasharray][][id]', 1],
|
86
|
+
['deep[deephasharray][][id]', 2],
|
87
|
+
].sort_by(&:join)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '::QUERY_STRING_NORMALIZER' do
|
93
|
+
|
94
|
+
subject { Http::Multipart::QUERY_STRING_NORMALIZER }
|
95
|
+
|
96
|
+
it 'should map a file to UploadIO' do
|
97
|
+
(first_k, first_v) = subject.call({
|
98
|
+
:file => file
|
99
|
+
}).first
|
100
|
+
|
101
|
+
first_v.should be_an UploadIO
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should map a Tempfile to UploadIO' do
|
105
|
+
(first_k, first_v) = subject.call({
|
106
|
+
:file => tempfile
|
107
|
+
}).first
|
108
|
+
|
109
|
+
first_v.should be_an UploadIO
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should map an array of files to UploadIOs' do
|
113
|
+
subject.call({
|
114
|
+
:file => [file, tempfile]
|
115
|
+
}).each { |(k,v)| v.should be_an UploadIO }
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'angus/remote/http/query_params'
|
4
|
+
|
5
|
+
describe Http::QueryParams do
|
6
|
+
|
7
|
+
describe '.to_params' do
|
8
|
+
|
9
|
+
let(:params) {
|
10
|
+
{ :name => 'Bob',
|
11
|
+
:address => {
|
12
|
+
:phones => %w[111-111-1111 222-222-2222],
|
13
|
+
:street => '111 Ruby Ave.',
|
14
|
+
:zone => {
|
15
|
+
:country => 'Ruby',
|
16
|
+
:city => 'Gem Central',
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
it 'returns the expected string' do
|
23
|
+
Http::QueryParams.to_params(params).should eq('name=Bob&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111%20Ruby%20Ave.&address[zone][country]=Ruby&address[zone][city]=Gem%20Central')
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'angus/remote/proxy_client_utils'
|
6
|
+
|
7
|
+
describe Angus::Remote::ProxyClientUtils do
|
8
|
+
|
9
|
+
subject(:utils) { Angus::Remote::ProxyClientUtils }
|
10
|
+
|
11
|
+
describe '.build_request' do
|
12
|
+
|
13
|
+
let(:path) { '/' }
|
14
|
+
let(:query) { 'q=listing' }
|
15
|
+
|
16
|
+
shared_examples 'a request builder' do |method, kind_of|
|
17
|
+
context "when #{method}" do
|
18
|
+
it "returns a kind_of #{kind_of}" do
|
19
|
+
res = utils.build_request(method, path, query)
|
20
|
+
|
21
|
+
res.should be_a(kind_of)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it_behaves_like 'a request builder', :get, Net::HTTP::Get
|
27
|
+
it_behaves_like 'a request builder', :post, Net::HTTP::Post
|
28
|
+
it_behaves_like 'a request builder', :put, Net::HTTP::Put
|
29
|
+
it_behaves_like 'a request builder', :delete, Net::HTTP::Delete
|
30
|
+
|
31
|
+
context 'with headers' do
|
32
|
+
it 'sets the to the request' do
|
33
|
+
headers = { 'a' => 'A', 'b' => 'B' }
|
34
|
+
|
35
|
+
res = utils.build_request(:get, path, query, headers)
|
36
|
+
|
37
|
+
res['a'].should eq('A')
|
38
|
+
res['b'].should eq('B')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with body' do
|
43
|
+
it 'sets the body to the request' do
|
44
|
+
body = 'BODY'
|
45
|
+
|
46
|
+
res = utils.build_request(:get, path, query, {}, body)
|
47
|
+
|
48
|
+
res.body.should eq(body)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when invalid http method' do
|
53
|
+
it 'raises MethodArgumentError' do
|
54
|
+
expect {
|
55
|
+
utils.build_request(:invalid, path, query)
|
56
|
+
}.to raise_error(Angus::Remote::MethodArgumentError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.filter_response_headers' do
|
62
|
+
|
63
|
+
it 'rejects non allowed headers' do
|
64
|
+
headers = {:not_allowed => 'header'}
|
65
|
+
|
66
|
+
res = utils.filter_response_headers(headers)
|
67
|
+
|
68
|
+
res.should_not include(:not_allowed)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'does not reject allowed headers' do
|
72
|
+
headers = {'content-type' => 'header'}
|
73
|
+
|
74
|
+
res = utils.filter_response_headers(headers)
|
75
|
+
|
76
|
+
res.should include('content-type')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '.normalize_headers' do
|
81
|
+
context 'when a header value is an array' do
|
82
|
+
it 'takes the first array element' do
|
83
|
+
headers = {'content-type' => ['application/json', 'image/gif']}
|
84
|
+
|
85
|
+
res = utils.normalize_headers(headers)
|
86
|
+
|
87
|
+
res.should include('content-type' => 'application/json')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when simple headers' do
|
92
|
+
it 'does not affect anything' do
|
93
|
+
headers = {'content-type' => 'application/json'}
|
94
|
+
|
95
|
+
res = utils.normalize_headers(headers)
|
96
|
+
|
97
|
+
res.should include('content-type' => 'application/json')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|