callapi 0.8
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +207 -0
- data/Rakefile +2 -0
- data/callapi.gemspec +31 -0
- data/lib/callapi.rb +12 -0
- data/lib/callapi/call.rb +3 -0
- data/lib/callapi/call/base.rb +66 -0
- data/lib/callapi/call/request.rb +14 -0
- data/lib/callapi/call/request/api.rb +9 -0
- data/lib/callapi/call/request/base.rb +13 -0
- data/lib/callapi/call/request/http.rb +60 -0
- data/lib/callapi/call/request/http/log_helper.rb +41 -0
- data/lib/callapi/call/request/mock.rb +29 -0
- data/lib/callapi/call/request_metadata.rb +57 -0
- data/lib/callapi/call/response.rb +54 -0
- data/lib/callapi/call/response/json.rb +18 -0
- data/lib/callapi/call/response/json/as_object.rb +49 -0
- data/lib/callapi/call/response/plain.rb +5 -0
- data/lib/callapi/config.rb +56 -0
- data/lib/callapi/errors.rb +58 -0
- data/lib/callapi/routes.rb +131 -0
- data/lib/callapi/routes/metadata.rb +72 -0
- data/lib/callapi/version.rb +3 -0
- data/lib/ext/deep_struct.rb +25 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/unit/call/base_spec.rb +38 -0
- data/spec/unit/call/request/api_spec.rb +41 -0
- data/spec/unit/call/request/mock_spec.rb +38 -0
- data/spec/unit/call/request_metadata_spec.rb +58 -0
- data/spec/unit/call/response/json/as_object_spec.rb +48 -0
- data/spec/unit/call/response/json_spec.rb +21 -0
- data/spec/unit/call/response/plain_spec.rb +23 -0
- data/spec/unit/call/response_spec.rb +61 -0
- data/spec/unit/routes_spec.rb +123 -0
- metadata +216 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
class Callapi::Routes::Metadata
|
2
|
+
extend Memoist
|
3
|
+
|
4
|
+
def initialize(http_method_namespace, *args)
|
5
|
+
@http_method_namespace = http_method_namespace
|
6
|
+
@call_name, @call_options = args.shift, *args
|
7
|
+
|
8
|
+
call_name_with_all_namespaces.size.times do |i|
|
9
|
+
metadata = create_metadata(i)
|
10
|
+
Callapi::Routes.send(:save_class, metadata) if metadata
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def create_metadata(i)
|
17
|
+
new_class_name = call_name_with_all_namespaces[i + 1]
|
18
|
+
|
19
|
+
return unless new_class_name
|
20
|
+
|
21
|
+
is_call_class = new_class_name.eql? call_name_with_all_namespaces.last
|
22
|
+
|
23
|
+
OpenStruct.new.tap do |data|
|
24
|
+
data.class_name = full_class_name(i)
|
25
|
+
data.call_class = is_call_class
|
26
|
+
data.http_method_namespace = @http_method_namespace
|
27
|
+
if is_call_class
|
28
|
+
data.class_options = @call_options
|
29
|
+
data.call_name_with_namespaces = call_name_with_namespaces
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def full_class_name(i)
|
35
|
+
call_name_with_all_namespaces[0..i + 1].join('::')
|
36
|
+
end
|
37
|
+
|
38
|
+
def call_name_with_namespaces
|
39
|
+
namespaces.push(@call_name).map do |class_name|
|
40
|
+
if class_name_with_param_key?(class_name)
|
41
|
+
class_name_to_class_name_with_param_key(class_name)
|
42
|
+
else
|
43
|
+
class_name
|
44
|
+
end.camelize
|
45
|
+
end
|
46
|
+
end
|
47
|
+
memoize :call_name_with_namespaces
|
48
|
+
|
49
|
+
def call_name_with_all_namespaces
|
50
|
+
[@http_method_namespace.to_s] + call_name_with_namespaces
|
51
|
+
end
|
52
|
+
memoize :call_name_with_all_namespaces
|
53
|
+
|
54
|
+
def namespaces
|
55
|
+
Callapi::Routes.send(:namespaces).dup
|
56
|
+
end
|
57
|
+
memoize :namespaces
|
58
|
+
|
59
|
+
def class_name_with_param_key?(class_name)
|
60
|
+
class_name[0] == ':' || class_name.match('/:')
|
61
|
+
end
|
62
|
+
|
63
|
+
def class_name_to_class_name_with_param_key(class_name)
|
64
|
+
class_name.scan(/(:\w+)\/?/).map(&:first).each do |pattern|
|
65
|
+
replacement = pattern.dup
|
66
|
+
replacement[0] = ''
|
67
|
+
replacement << '_param'
|
68
|
+
class_name.sub!(pattern, replacement)
|
69
|
+
end
|
70
|
+
class_name
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class DeepStruct < OpenStruct
|
2
|
+
def initialize(hash = nil)
|
3
|
+
@table = {}
|
4
|
+
@hash_table = {}
|
5
|
+
|
6
|
+
if hash
|
7
|
+
hash.each do |key, value|
|
8
|
+
@table[key.to_sym] = process_value(value)
|
9
|
+
@hash_table[key.to_sym] = value
|
10
|
+
|
11
|
+
new_ostruct_member(key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_value(value)
|
17
|
+
if value.is_a?(Hash)
|
18
|
+
self.class.new(value)
|
19
|
+
elsif value.is_a?(Array)
|
20
|
+
value.map { |element| process_value(element) }
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
|
3
|
+
require_relative '../lib/callapi'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.before(:suite) do
|
7
|
+
Get = Module.new
|
8
|
+
Get::Users = Class.new(Callapi::Call::Base)
|
9
|
+
|
10
|
+
Callapi::Config.configure do |config|
|
11
|
+
config.log_level = :off
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Base do
|
4
|
+
let(:config) { Callapi::Config }
|
5
|
+
let(:call) { described_class.new }
|
6
|
+
|
7
|
+
context '#initialize' do
|
8
|
+
it 'should take params as an argument' do
|
9
|
+
expect(described_class.new(some_param: :some_value).params).to eql(some_param: :some_value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context '#strategy' do
|
14
|
+
before { allow(config).to receive(:request_strategy).and_return(Callapi::Call::Request::Api) }
|
15
|
+
|
16
|
+
it 'should be taken from config' do
|
17
|
+
expect(subject.strategy).to eql(Callapi::Call::Request::Api)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context '.strategy' do
|
22
|
+
before { described_class.strategy = Callapi::Call::Request::Mock }
|
23
|
+
|
24
|
+
it 'should overwrite default strategy for each instance' do
|
25
|
+
expect(subject.strategy).to eql(Callapi::Call::Request::Mock)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#response_parser' do
|
30
|
+
subject { call.response_parser }
|
31
|
+
|
32
|
+
before { call.with_response_parser(Callapi::Call::Parser::Json::AsObject) }
|
33
|
+
|
34
|
+
it 'should be accessible' do
|
35
|
+
expect(subject).to eql(Callapi::Call::Parser::Json::AsObject)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Request::Api do
|
4
|
+
context '#host' do
|
5
|
+
subject { described_class.new(nil).host }
|
6
|
+
let(:config) { Callapi::Config }
|
7
|
+
|
8
|
+
context 'when API host is not set' do
|
9
|
+
it 'should raise ApiHostNotSet error' do
|
10
|
+
expect { subject }.to raise_error { Callapi::ApiHostNotSetError }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when API host is set' do
|
15
|
+
before { allow(config).to receive(:api_host).and_return('http://api.org') }
|
16
|
+
|
17
|
+
it 'should take API host from config' do
|
18
|
+
expect( subject ).to eql 'http://api.org'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context '#response' do
|
24
|
+
before do
|
25
|
+
config = Callapi::Config
|
26
|
+
allow(config).to receive(:request_strategy).and_return(described_class)
|
27
|
+
allow(config).to receive(:api_host).and_return('http://api.org')
|
28
|
+
stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '')
|
29
|
+
|
30
|
+
call_class = Get::Users
|
31
|
+
@call = call_class.new
|
32
|
+
end
|
33
|
+
|
34
|
+
subject { described_class.new(@call).response }
|
35
|
+
|
36
|
+
it 'should get response from server' do
|
37
|
+
expect( subject.body ).to eql(nil)
|
38
|
+
expect( subject.code ).to eql('200')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Request::Mock do
|
4
|
+
context '#response' do
|
5
|
+
before do
|
6
|
+
call_class = Get::Users
|
7
|
+
@call = call_class.new
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { described_class.new(@call).response }
|
11
|
+
|
12
|
+
context 'when there is no file with mocked response' do
|
13
|
+
it 'should raise CouldNotFoundRequestMockFile error' do
|
14
|
+
expect{ subject }.to raise_error { Callapi::CouldNotFoundMockRequestFileError }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#body' do
|
19
|
+
subject { described_class.new(@call).response.body }
|
20
|
+
|
21
|
+
before do
|
22
|
+
allow(File).to receive(:read).and_return('body from file')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should be taken from file' do
|
26
|
+
expect(subject).to eql('body from file')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context '#filepath' do
|
31
|
+
subject { described_class.new(@call).send(:file_path) }
|
32
|
+
|
33
|
+
it 'should be created based on call HTTP method, request path and response format ' do
|
34
|
+
expect(subject).to eql 'mocked_calls/get/users.json'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::RequestMetadata do
|
4
|
+
before(:all) do
|
5
|
+
Get::Users::Details = Class.new(Callapi::Call::Base)
|
6
|
+
Get::Users::IdParam = Class.new(Callapi::Call::Base)
|
7
|
+
Get::Users::IdParam::Posts = Class.new(Callapi::Call::Base)
|
8
|
+
Get::Users::IdParam::Posts::PostIdParam = Class.new(Callapi::Call::Base)
|
9
|
+
ClassWithoutHttpNamespace = Class.new(Callapi::Call::Base)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:metadata) { described_class.new(@call_class.new) }
|
13
|
+
|
14
|
+
context '#request_method' do
|
15
|
+
subject { metadata.request_method }
|
16
|
+
|
17
|
+
context 'when call class has namespace equal to some HTTP method' do
|
18
|
+
let(:metadata) { described_class.new(Get::Users.new) }
|
19
|
+
|
20
|
+
it 'should take HTTP method from this namespace' do
|
21
|
+
expect(subject).to eql(:get)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when call class does not have namespace equal to some HTTP method' do
|
26
|
+
let(:metadata) { described_class.new(ClassWithoutHttpNamespace.new) }
|
27
|
+
|
28
|
+
it 'should raise UnknownHttpMethod error' do
|
29
|
+
expect{ subject }.to raise_error { Callapi::UnknownHttpMethodError }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context '#request_path' do
|
35
|
+
subject { metadata.request_path }
|
36
|
+
let(:metadata) { described_class.new(Get::Users::Details.new) }
|
37
|
+
|
38
|
+
it 'should be equal to underscored class name with its namespaces' do
|
39
|
+
expect(subject).to eql('/users/details')
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when class name contain "Param" string' do
|
43
|
+
let(:metadata) { described_class.new(Get::Users::IdParam::Posts::PostIdParam.new(id: 123, post_id: 456)) }
|
44
|
+
|
45
|
+
it 'should use this param value to create path' do
|
46
|
+
expect(subject).to eql('/users/123/posts/456')
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'but required param is not provided' do
|
50
|
+
let(:metadata) { described_class.new(Get::Users::IdParam::Posts::PostIdParam.new(post_id: 456)) }
|
51
|
+
|
52
|
+
it 'should raise MissingParam error' do
|
53
|
+
expect{ subject }.to raise_error{Callapi::MissingParamError}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Parser::Json::AsObject do
|
4
|
+
context '#data' do
|
5
|
+
before do
|
6
|
+
config = Callapi::Config
|
7
|
+
allow(config).to receive(:api_host).and_return('http://api.org')
|
8
|
+
allow(config).to receive(:default_response_parser).and_return(described_class)
|
9
|
+
|
10
|
+
@call = Get::Users.new
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { @call.response.data }
|
14
|
+
|
15
|
+
context 'when request body is "{"valid": true, "score": 59}"' do
|
16
|
+
before { stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '{"valid": true, "score": 59}') }
|
17
|
+
|
18
|
+
it 'should return object with #valid and #score methods' do
|
19
|
+
expect(subject.valid).to eq (true)
|
20
|
+
expect(subject.score).to eq (59)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when request body is "[{"score": 59}, {"score": 60}]"' do
|
25
|
+
before { stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '[{"score": 59}, {"score": 60}]') }
|
26
|
+
|
27
|
+
it 'should return array of objects with #score method' do
|
28
|
+
expect(subject[0].score).to eq (59)
|
29
|
+
expect(subject[1].score).to eq (60)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context '#keys_excluded_from_parsing' do
|
34
|
+
context 'when set to ["translations"]' do
|
35
|
+
before { described_class.keys_excluded_from_parsing = ['translations'] }
|
36
|
+
|
37
|
+
context 'and request body is "{"valid": true, "translations": {"a": "A"}}"' do
|
38
|
+
before { stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '{"valid": true, "translations": {"a": "A"}}') }
|
39
|
+
|
40
|
+
it 'should should not create object from #translations' do
|
41
|
+
expect(subject.valid).to eq (true)
|
42
|
+
expect(subject.translations).to eq ({'a' => 'A'})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Parser::Json do
|
4
|
+
context '#data' do
|
5
|
+
before do
|
6
|
+
config = Callapi::Config
|
7
|
+
allow(config).to receive(:api_host).and_return('http://api.org')
|
8
|
+
|
9
|
+
call_class = Get::Users
|
10
|
+
@call = call_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { @call.response.data }
|
14
|
+
|
15
|
+
context 'when request body is "{"valid": true, "score": 59}"' do
|
16
|
+
before { stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '{"valid": true, "score": 59}') }
|
17
|
+
|
18
|
+
it { expect(subject).to eq ({'valid' => true, 'score' => 59}) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Parser::Plain do
|
4
|
+
context '#data' do
|
5
|
+
before do
|
6
|
+
Callapi::Config.configure do |config|
|
7
|
+
config.api_host = 'http://api.org'
|
8
|
+
end
|
9
|
+
|
10
|
+
Callapi::Routes.draw do
|
11
|
+
get 'plain', parser: Callapi::Call::Parser::Plain
|
12
|
+
end
|
13
|
+
|
14
|
+
stub_request(:get, 'http://api.org/plain').to_return(status: 200, body: 'some response')
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { get_plain_call.response.data }
|
18
|
+
|
19
|
+
it 'should return response body' do
|
20
|
+
expect(subject).to eql ( 'some response' )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Callapi::Call::Parser do
|
4
|
+
before do
|
5
|
+
config = Callapi::Config
|
6
|
+
allow(config).to receive(:api_host).and_return('http://api.org')
|
7
|
+
|
8
|
+
@call = Get::Users.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'caching response' do
|
12
|
+
before do
|
13
|
+
stub_request(:get, 'http://api.org/users').to_return(status: 200, body: '{"json": true}')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should not parse response twice unless call is reloaded' do
|
17
|
+
cached_response = {'json' => true}
|
18
|
+
expect(@call.response.data).to eql cached_response
|
19
|
+
@call.response_parser = Callapi::Call::Parser::Plain
|
20
|
+
expect(@call.response.data).to eql cached_response
|
21
|
+
@call.reload
|
22
|
+
expect(@call.response.data).to eql '{"json": true}'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context '#data' do
|
28
|
+
subject { @call.response.data }
|
29
|
+
|
30
|
+
context 'when API returned 5xx' do
|
31
|
+
before do
|
32
|
+
stub_request(:get, 'http://api.org/users').to_return(status: 500)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should raise ServerError error' do
|
36
|
+
expect{ subject }.to raise_error { Callapi::ServerError }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when API returned 4xx' do
|
41
|
+
before do
|
42
|
+
stub_request(:get, 'http://api.org/users').to_return(status: 400)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise ClientError error' do
|
46
|
+
expect{ subject }.to raise_error { Callapi::ClientError }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when API returned 401' do
|
51
|
+
before do
|
52
|
+
stub_request(:get, 'http://api.org/users').to_return(status: 401)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should raise NotAuthorized error' do
|
56
|
+
expect{ subject }.to raise_error { Callapi::NotAuthorizedError }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|