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.
Files changed (43) hide show
  1. data/.gitignore +1 -0
  2. data/.irbrc +3 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +0 -1
  5. data/Gemfile +1 -3
  6. data/README.md +138 -125
  7. data/frenetic.gemspec +5 -6
  8. data/lib/frenetic.rb +31 -43
  9. data/lib/frenetic/concerns/collection_rest_methods.rb +13 -0
  10. data/lib/frenetic/concerns/configurable.rb +59 -0
  11. data/lib/frenetic/concerns/hal_linked.rb +59 -0
  12. data/lib/frenetic/concerns/member_rest_methods.rb +15 -0
  13. data/lib/frenetic/concerns/structured.rb +53 -0
  14. data/lib/frenetic/configuration.rb +40 -76
  15. data/lib/frenetic/middleware/hal_json.rb +23 -0
  16. data/lib/frenetic/resource.rb +77 -62
  17. data/lib/frenetic/resource_collection.rb +46 -0
  18. data/lib/frenetic/version.rb +2 -2
  19. data/spec/concerns/configurable_spec.rb +50 -0
  20. data/spec/concerns/hal_linked_spec.rb +116 -0
  21. data/spec/concerns/member_rest_methods_spec.rb +41 -0
  22. data/spec/concerns/structured_spec.rb +214 -0
  23. data/spec/configuration_spec.rb +99 -0
  24. data/spec/fixtures/test_api_requests.rb +88 -0
  25. data/spec/frenetic_spec.rb +137 -0
  26. data/spec/middleware/hal_json_spec.rb +83 -0
  27. data/spec/resource_collection_spec.rb +80 -0
  28. data/spec/resource_spec.rb +211 -0
  29. data/spec/spec_helper.rb +4 -13
  30. data/spec/support/rspec.rb +5 -0
  31. data/spec/support/webmock.rb +3 -0
  32. metadata +59 -57
  33. data/.rvmrc +0 -1
  34. data/lib/frenetic/hal_json.rb +0 -23
  35. data/lib/frenetic/hal_json/response_wrapper.rb +0 -43
  36. data/lib/recursive_open_struct.rb +0 -79
  37. data/spec/fixtures/vcr_cassettes/description_error_unauthorized.yml +0 -36
  38. data/spec/fixtures/vcr_cassettes/description_success.yml +0 -38
  39. data/spec/lib/frenetic/configuration_spec.rb +0 -189
  40. data/spec/lib/frenetic/hal_json/response_wrapper_spec.rb +0 -70
  41. data/spec/lib/frenetic/hal_json_spec.rb +0 -68
  42. data/spec/lib/frenetic/resource_spec.rb +0 -182
  43. 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