frenetic 0.0.12 → 0.0.20.alpha.0
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 +1 -0
- data/.irbrc +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +0 -1
- data/Gemfile +1 -3
- data/README.md +138 -125
- data/frenetic.gemspec +5 -6
- data/lib/frenetic.rb +31 -43
- data/lib/frenetic/concerns/collection_rest_methods.rb +13 -0
- data/lib/frenetic/concerns/configurable.rb +59 -0
- data/lib/frenetic/concerns/hal_linked.rb +59 -0
- data/lib/frenetic/concerns/member_rest_methods.rb +15 -0
- data/lib/frenetic/concerns/structured.rb +53 -0
- data/lib/frenetic/configuration.rb +40 -76
- data/lib/frenetic/middleware/hal_json.rb +23 -0
- data/lib/frenetic/resource.rb +77 -62
- data/lib/frenetic/resource_collection.rb +46 -0
- data/lib/frenetic/version.rb +2 -2
- data/spec/concerns/configurable_spec.rb +50 -0
- data/spec/concerns/hal_linked_spec.rb +116 -0
- data/spec/concerns/member_rest_methods_spec.rb +41 -0
- data/spec/concerns/structured_spec.rb +214 -0
- data/spec/configuration_spec.rb +99 -0
- data/spec/fixtures/test_api_requests.rb +88 -0
- data/spec/frenetic_spec.rb +137 -0
- data/spec/middleware/hal_json_spec.rb +83 -0
- data/spec/resource_collection_spec.rb +80 -0
- data/spec/resource_spec.rb +211 -0
- data/spec/spec_helper.rb +4 -13
- data/spec/support/rspec.rb +5 -0
- data/spec/support/webmock.rb +3 -0
- metadata +59 -57
- data/.rvmrc +0 -1
- data/lib/frenetic/hal_json.rb +0 -23
- data/lib/frenetic/hal_json/response_wrapper.rb +0 -43
- data/lib/recursive_open_struct.rb +0 -79
- data/spec/fixtures/vcr_cassettes/description_error_unauthorized.yml +0 -36
- data/spec/fixtures/vcr_cassettes/description_success.yml +0 -38
- data/spec/lib/frenetic/configuration_spec.rb +0 -189
- data/spec/lib/frenetic/hal_json/response_wrapper_spec.rb +0 -70
- data/spec/lib/frenetic/hal_json_spec.rb +0 -68
- data/spec/lib/frenetic/resource_spec.rb +0 -182
- data/spec/lib/frenetic_spec.rb +0 -129
@@ -0,0 +1,99 @@
|
|
1
|
+
describe Frenetic::Configuration do
|
2
|
+
let(:cfg) { Hash.new }
|
3
|
+
|
4
|
+
subject(:instance) { described_class.new cfg }
|
5
|
+
|
6
|
+
describe '#adapter' do
|
7
|
+
subject { instance.adapter }
|
8
|
+
|
9
|
+
it { should == :net_http }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#attributes' do
|
13
|
+
subject { instance.attributes }
|
14
|
+
|
15
|
+
it { should include :adapter }
|
16
|
+
it { should include :api_token }
|
17
|
+
it { should include :cache }
|
18
|
+
it { should include :headers }
|
19
|
+
it { should include :password }
|
20
|
+
it { should include :url }
|
21
|
+
it { should include :username }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#api_token' do
|
25
|
+
let(:cfg) do
|
26
|
+
{ api_token:'API_TOKEN' }
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { instance.api_token }
|
30
|
+
|
31
|
+
it { should == 'API_TOKEN' }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#cache' do
|
35
|
+
subject { instance.cache }
|
36
|
+
|
37
|
+
context 'with a value of :rack' do
|
38
|
+
let(:cfg) do
|
39
|
+
{ cache: :rack }
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should return Rack::Cache options' do
|
43
|
+
subject.should include metastore:'file:tmp/rack/meta'
|
44
|
+
subject.should include entitystore:'file:tmp/rack/body'
|
45
|
+
subject.should include ignore_headers:%w{Authorization Set-Cookie X-Content-Digest}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#headers' do
|
51
|
+
let(:cfg) do
|
52
|
+
{ headers:{ accept:'MIME', x_foo:'BAR' } }
|
53
|
+
end
|
54
|
+
|
55
|
+
subject { instance.headers }
|
56
|
+
|
57
|
+
it 'should properly merge in nested header values' do
|
58
|
+
subject.should include :user_agent
|
59
|
+
subject.should include accept:'MIME'
|
60
|
+
subject.should include x_foo:'BAR'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#password' do
|
65
|
+
subject { instance.password }
|
66
|
+
|
67
|
+
context 'with a specifed Api key' do
|
68
|
+
let(:cfg) do
|
69
|
+
{ api_key:'API_KEY' }
|
70
|
+
end
|
71
|
+
|
72
|
+
it { should == 'API_KEY' }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#url' do
|
77
|
+
let(:cfg) do
|
78
|
+
{ url:'http://example.org' }
|
79
|
+
end
|
80
|
+
|
81
|
+
subject { instance.url }
|
82
|
+
|
83
|
+
it { should be_a Addressable::URI }
|
84
|
+
|
85
|
+
its(:to_s) { should == 'http://example.org' }
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#username' do
|
89
|
+
subject { instance.username }
|
90
|
+
|
91
|
+
context 'with a specifed App Id' do
|
92
|
+
let(:cfg) do
|
93
|
+
{ app_id:'APP_ID' }
|
94
|
+
end
|
95
|
+
|
96
|
+
it { should == 'APP_ID' }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class HttpStubs
|
4
|
+
|
5
|
+
def initialize( rspec )
|
6
|
+
@rspec = rspec
|
7
|
+
end
|
8
|
+
|
9
|
+
def defaults
|
10
|
+
{
|
11
|
+
status: 200,
|
12
|
+
headers: { 'Content-Type'=>'application/json' },
|
13
|
+
body: {}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def response( params = {} )
|
18
|
+
defaults.merge( params ).tap do |p|
|
19
|
+
p[:body] = p[:body].to_json
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def api_server_error
|
24
|
+
@rspec.stub_request( :any, 'example.com/api' )
|
25
|
+
.to_return response( body:{error:'500 Server Error'}, status:500 )
|
26
|
+
end
|
27
|
+
|
28
|
+
def api_client_error( type = :json )
|
29
|
+
body = '404 Not Found'
|
30
|
+
|
31
|
+
body = { 'error' => body }.to_json if type == :json
|
32
|
+
|
33
|
+
@rspec.stub_request( :any, 'example.com/api' )
|
34
|
+
.to_return defaults.merge( body:body, status:404 )
|
35
|
+
end
|
36
|
+
|
37
|
+
def api_description
|
38
|
+
@rspec.stub_request( :any, 'example.com/api' )
|
39
|
+
.to_return response( body:schema )
|
40
|
+
end
|
41
|
+
|
42
|
+
def unknown_resource
|
43
|
+
@rspec.stub_request( :get, 'example.com/api/my_temp_resources/1' )
|
44
|
+
.to_return response( body:{ 'error' => '404 Not Found' }, status:404 )
|
45
|
+
end
|
46
|
+
|
47
|
+
def known_resource
|
48
|
+
@rspec.stub_request( :get, 'example.com/api/my_temp_resources/1' )
|
49
|
+
.to_return response( body:{ 'name' => 'Resource Name' } )
|
50
|
+
end
|
51
|
+
|
52
|
+
def schema
|
53
|
+
{
|
54
|
+
_embedded: {
|
55
|
+
schema: {
|
56
|
+
my_temp_resource: {
|
57
|
+
description: 'Humanized resource description',
|
58
|
+
properties: {
|
59
|
+
name: 'string'
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
},
|
64
|
+
_links: {
|
65
|
+
schema: {
|
66
|
+
href: '/api/schema'
|
67
|
+
},
|
68
|
+
my_temp_resources: {
|
69
|
+
href: '/api/my_temp_resources'
|
70
|
+
},
|
71
|
+
my_temp_resource: {
|
72
|
+
href: '/api/my_temp_resources/{id}',
|
73
|
+
templated: true
|
74
|
+
},
|
75
|
+
abstract_resource: {
|
76
|
+
href: '/api/abstract_resource'
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
RSpec.configure do |c|
|
85
|
+
c.before :all do
|
86
|
+
@stubs = HttpStubs.new( self )
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frenetic do
|
4
|
+
let(:test_cfg) do
|
5
|
+
{
|
6
|
+
url:'http://example.com/api'
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
subject(:instance) { described_class.new( test_cfg ) }
|
11
|
+
|
12
|
+
describe '#connection' do
|
13
|
+
subject { instance.connection }
|
14
|
+
|
15
|
+
it { should be_a Faraday::Connection }
|
16
|
+
|
17
|
+
context 'configured with a :username' do
|
18
|
+
let(:test_cfg) { super().merge username:'foo' }
|
19
|
+
|
20
|
+
subject { super().builder.handlers }
|
21
|
+
|
22
|
+
it { should include Faraday::Request::BasicAuthentication }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'configured with a :api_token' do
|
26
|
+
let(:test_cfg) { super().merge api_token:'API_TOKEN' }
|
27
|
+
|
28
|
+
subject { super().builder.handlers }
|
29
|
+
|
30
|
+
it { should include Faraday::Request::TokenAuthentication }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'configured to use Rack::Cache' do
|
34
|
+
let(:test_cfg) { super().merge cache: :rack }
|
35
|
+
|
36
|
+
subject { super().builder.handlers }
|
37
|
+
|
38
|
+
it { should include FaradayMiddleware::RackCompatible }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when Frenetic is initialized with a block' do
|
42
|
+
it 'should yield the Faraday builder to the block argument' do
|
43
|
+
builder = nil
|
44
|
+
|
45
|
+
described_class.new(test_cfg) do |b|
|
46
|
+
builder = b
|
47
|
+
end.connection
|
48
|
+
|
49
|
+
builder.should be_a Faraday::Connection
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#description' do
|
55
|
+
subject { instance.description }
|
56
|
+
|
57
|
+
context 'with a URL that returns a' do
|
58
|
+
context 'valid response' do
|
59
|
+
before { @stubs.api_description }
|
60
|
+
|
61
|
+
it { should include '_embedded', '_links' }
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'server error' do
|
65
|
+
before { @stubs.api_server_error }
|
66
|
+
|
67
|
+
it 'should raise an error' do
|
68
|
+
expect{ subject }.to raise_error Frenetic::ServerError
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'client error' do
|
73
|
+
before { @stubs.api_client_error :json }
|
74
|
+
|
75
|
+
it 'should raise an error' do
|
76
|
+
expect{ subject }.to raise_error Frenetic::ClientError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'JSON parsing error' do
|
81
|
+
before { @stubs.api_client_error :text }
|
82
|
+
|
83
|
+
it 'should raise an error' do
|
84
|
+
expect{ subject }.to raise_error Frenetic::ParsingError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '.schema' do
|
91
|
+
subject { instance.schema }
|
92
|
+
|
93
|
+
before { @stubs.api_description }
|
94
|
+
|
95
|
+
it { should include 'my_temp_resource' }
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#get' do
|
99
|
+
subject { instance.get '/foo' }
|
100
|
+
|
101
|
+
it 'should delegate to Faraday' do
|
102
|
+
instance.connection.should_receive :get
|
103
|
+
|
104
|
+
subject
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#put' do
|
109
|
+
subject { instance.put '/foo' }
|
110
|
+
|
111
|
+
it 'should delegate to Faraday' do
|
112
|
+
instance.connection.should_receive :put
|
113
|
+
|
114
|
+
subject
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#post' do
|
119
|
+
subject { instance.post '/foo' }
|
120
|
+
|
121
|
+
it 'should delegate to Faraday' do
|
122
|
+
instance.connection.should_receive :post
|
123
|
+
|
124
|
+
subject
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '#delete' do
|
129
|
+
subject { instance.delete '/foo' }
|
130
|
+
|
131
|
+
it 'should delegate to Faraday' do
|
132
|
+
instance.connection.should_receive :delete
|
133
|
+
|
134
|
+
subject
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frenetic::Middleware::HalJson do
|
4
|
+
|
5
|
+
def process(body, content_type = nil, options = {}, status = 200)
|
6
|
+
env = {
|
7
|
+
body: body,
|
8
|
+
request: options,
|
9
|
+
response_headers: Faraday::Utils::Headers.new( headers ),
|
10
|
+
status: status
|
11
|
+
}
|
12
|
+
env[:response_headers]['content-type'] = content_type if content_type
|
13
|
+
|
14
|
+
middleware.call env
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:options) { Hash.new }
|
18
|
+
|
19
|
+
let(:headers) { Hash.new }
|
20
|
+
|
21
|
+
let(:middleware) do
|
22
|
+
described_class.new(lambda {|env|
|
23
|
+
Faraday::Response.new(env)
|
24
|
+
}, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does not change nil body' do
|
28
|
+
expect(process(nil).body).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'nullifies empty body' do
|
32
|
+
expect(process('').body).to be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with a HAL+JSON body' do
|
36
|
+
let(:body) do
|
37
|
+
{
|
38
|
+
'name' => 'My Name',
|
39
|
+
'_embedded' => {
|
40
|
+
'other_resource' => {
|
41
|
+
'label' => 'My Label'
|
42
|
+
}
|
43
|
+
},
|
44
|
+
'_links' => {
|
45
|
+
'self' => {
|
46
|
+
'href' => '/api/my_temp_resource/1'
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}.to_json
|
50
|
+
end
|
51
|
+
|
52
|
+
subject { process(body) }
|
53
|
+
|
54
|
+
it 'should parse the body' do
|
55
|
+
expect(subject.body).to include 'name' => 'My Name'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with error response' do
|
60
|
+
subject { process(error, nil, {}, status) }
|
61
|
+
|
62
|
+
context 'from the server' do
|
63
|
+
let(:status) { 500 }
|
64
|
+
|
65
|
+
let(:error) { { 'status' => status.to_s, error:'500 Server Error' }.to_json }
|
66
|
+
|
67
|
+
it 'should raise an error' do
|
68
|
+
expect{ subject }.to raise_error Frenetic::ServerError
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'cause by the client' do
|
73
|
+
let(:status) { 404 }
|
74
|
+
|
75
|
+
let(:error) { { 'status' => status.to_s, error:'404 Not Found' }.to_json }
|
76
|
+
|
77
|
+
it 'should raise an error' do
|
78
|
+
expect{ subject }.to raise_error Frenetic::ClientError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frenetic::ResourceCollection do
|
4
|
+
let(:test_cfg) do
|
5
|
+
{
|
6
|
+
url:'http://example.com/api'
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:my_temp_resource) do
|
11
|
+
cfg = test_cfg
|
12
|
+
|
13
|
+
Class.new(Frenetic::Resource) do
|
14
|
+
api_client { Frenetic.new(cfg) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
stub_const 'MyTempResource', my_temp_resource
|
20
|
+
|
21
|
+
@stubs.api_description
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:collection_response) {
|
25
|
+
{
|
26
|
+
'_embedded' => {
|
27
|
+
'my_temp_resources' => [
|
28
|
+
{ 'id' => 1, 'name' => 'First' },
|
29
|
+
{ 'id' => 2, 'name' => 'Last' }
|
30
|
+
]
|
31
|
+
},
|
32
|
+
'_links' => {
|
33
|
+
'self' => {
|
34
|
+
'href' => '/api/my_temp_resources'
|
35
|
+
},
|
36
|
+
'my_temp_resource' => {
|
37
|
+
'href' => '/api/my_temp_resources/{id}',
|
38
|
+
'templated' => true
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
subject(:instance) { described_class.new(MyTempResource, collection_response) }
|
45
|
+
|
46
|
+
it 'should know where the resources are located' do
|
47
|
+
subject.collection_key.should == 'my_temp_resources'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should know which resource it represents' do
|
51
|
+
subject.resource_type.should == 'my_temp_resource'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should extract the embedded resources' do
|
55
|
+
subject.size.should == 2
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should parse the embedded resources' do
|
59
|
+
subject.first.should be_a MyTempResource
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should be able to make API calls' do
|
63
|
+
subject.api.should be_an_instance_of Frenetic
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should have links' do
|
67
|
+
subject.links.should_not be_empty
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#get' do
|
71
|
+
before { @stubs.known_resource }
|
72
|
+
|
73
|
+
subject { super().get(1) }
|
74
|
+
|
75
|
+
it 'should GET the full representation' do
|
76
|
+
subject.should be_an_instance_of MyTempResource
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|