angus-remote 0.0.1 → 0.0.2
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/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
|