frenetic 1.0.0 → 2.0.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.
@@ -11,15 +11,25 @@ describe Frenetic::MemberRestMethods do
11
11
  end
12
12
  end
13
13
 
14
+ let(:my_filtered_resource) do
15
+ cfg = test_cfg
16
+
17
+ Class.new(Frenetic::Resource) do
18
+ api_client { Frenetic.new(cfg) }
19
+ end
20
+ end
21
+
14
22
  before do
15
23
  stub_const 'MyTempResource', my_temp_resource
16
24
  MyTempResource.send :include, described_class
17
25
  end
18
26
 
19
27
  describe '.find' do
28
+ let(:params) { 1 }
29
+
20
30
  before { @stubs.api_description }
21
31
 
22
- subject { MyTempResource.find 1 }
32
+ subject { MyTempResource.find(params) }
23
33
 
24
34
  context 'for a known instance' do
25
35
  before { @stubs.known_instance }
@@ -41,7 +51,33 @@ describe Frenetic::MemberRestMethods do
41
51
  before { @stubs.unknown_instance }
42
52
 
43
53
  it 'raises an error' do
44
- expect{subject}.to raise_error Frenetic::ClientError
54
+ expect{subject}.to(
55
+ raise_error Frenetic::ResourceNotFound, "Couldn't find MyTempResource with id=1"
56
+ )
57
+ end
58
+
59
+ context 'with an unparseable response' do
60
+ before { @stubs.invalid_unknown_instance }
61
+
62
+ it 'raises an error' do
63
+ expect{subject}.to(
64
+ raise_error Frenetic::ResourceNotFound, "Couldn't find MyTempResource with id=1"
65
+ )
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'that results in a client-level error' do
71
+ before do
72
+ @stubs.api_error(
73
+ status: 422,
74
+ body: 'Unprocessable Entity',
75
+ url: 'example.com/api/my_temp_resources/1'
76
+ )
77
+ end
78
+
79
+ it 'raises an error' do
80
+ expect{subject}.to raise_error Frenetic::ClientError, '422 Unprocessable Entity'
45
81
  end
46
82
  end
47
83
 
@@ -56,6 +92,35 @@ describe Frenetic::MemberRestMethods do
56
92
  it 'returns a mock resource' do
57
93
  expect(subject).to be_an_instance_of MyMockResource
58
94
  end
95
+
96
+ context 'with blank parameters' do
97
+ let(:params) { {} }
98
+
99
+ it 'raises an error' do
100
+ expect{subject}.to(
101
+ raise_error Frenetic::ResourceNotFound, "Couldn't find MyTempResource without an ID"
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '.find_by' do
109
+ before { @stubs.api_description }
110
+
111
+ subject { MyTempResource.find_by(id:1) }
112
+
113
+ it 'delegates to .find' do
114
+ allow(MyTempResource).to receive(:find).and_return(nil)
115
+ subject
116
+ expect(MyTempResource).to have_received(:find)
117
+ end
118
+
119
+ context 'for an unknown resource' do
120
+ it 'returns nil' do
121
+ allow(MyTempResource).to receive(:find).and_raise(Frenetic::ClientError.new({}))
122
+ expect{subject}.to_not raise_error
123
+ end
59
124
  end
60
125
  end
61
126
 
@@ -83,5 +148,23 @@ describe Frenetic::MemberRestMethods do
83
148
  expect(subject).to be_empty
84
149
  end
85
150
  end
151
+
152
+ context 'with additional parameters' do
153
+ before do
154
+ stub_const('MyFilteredResource', my_filtered_resource)
155
+ MyFilteredResource.send(:include, described_class)
156
+ @stubs.filtered_resource
157
+ end
158
+
159
+ subject { MyFilteredResource.all(filter: 'bar') }
160
+
161
+ it 'returns a resource collection' do
162
+ expect(subject).to be_an_instance_of Frenetic::ResourceCollection
163
+ end
164
+
165
+ it 'instantiates all resources in the collection' do
166
+ expect(subject.first).to be_an_instance_of MyFilteredResource
167
+ end
168
+ end
86
169
  end
87
170
  end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::Persistence do
4
+ let(:test_cfg) { { url:'http://example.com/api' } }
5
+
6
+ let(:my_temp_resource) do
7
+ cfg = test_cfg
8
+
9
+ Class.new(Frenetic::Resource) do
10
+ api_client { Frenetic.new(cfg) }
11
+ end
12
+ end
13
+
14
+ before do
15
+ stub_const 'MyTempResource', my_temp_resource
16
+ MyTempResource.send(:include, described_class)
17
+ end
18
+
19
+ subject(:instance) { MyTempResource.new }
20
+
21
+ describe '#save' do
22
+ before { @stubs.api_description }
23
+
24
+ subject { super().save }
25
+
26
+ context 'for a valid resource' do
27
+ before { @stubs.valid_created_resource }
28
+
29
+ it 'returns TRUE' do
30
+ expect(subject).to eql true
31
+ end
32
+
33
+ it 'returns a valid instance' do
34
+ subject
35
+ expect(instance.valid?).to eql true
36
+ end
37
+ end
38
+
39
+ context 'for an invalid resource' do
40
+ before { @stubs.invalid_created_resource }
41
+
42
+ it 'returns FALSE' do
43
+ expect(subject).to eql false
44
+ end
45
+
46
+ it 'returns an invalid instance' do
47
+ subject
48
+ expect(instance.valid?).to eql false
49
+ end
50
+
51
+ it 'maps resource errors onto the instance' do
52
+ subject
53
+ expect(instance.errors).to include 'name' => ['cannot be blank', 'must be a name']
54
+ end
55
+ end
56
+
57
+ context 'that triggers an exception' do
58
+ let(:mock_api) { double('api_client') }
59
+
60
+ before do
61
+ @stubs.valid_created_resource
62
+ allow(mock_api).to receive(:post).and_raise(StandardError)
63
+ expect(instance).to receive(:api).and_return(mock_api)
64
+ end
65
+
66
+ it 're-raises the exception' do
67
+ expect{subject}.to raise_error(StandardError)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#save!' do
73
+ before { @stubs.api_description }
74
+
75
+ subject { super().save! }
76
+
77
+ context 'for a valid resource' do
78
+ before { @stubs.valid_created_resource }
79
+
80
+ it 'returns TRUE' do
81
+ expect(subject).to eql true
82
+ end
83
+ end
84
+
85
+ context 'for an invalid resource' do
86
+ before { @stubs.invalid_created_resource }
87
+
88
+ it 'raises an error' do
89
+ # rubocop:disable Metrics/LineLength
90
+ expect{subject}.to raise_error(Frenetic::ClientError, 'Validation failed: Name cannot be blank, Name must be a name, Cannot be valid')
91
+ # rubocop:enable Metrics/LineLength
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  class HttpStubs
4
5
  def initialize(rspec)
5
6
  @rspec = rspec
@@ -29,22 +30,12 @@ class HttpStubs
29
30
  .to_return response(body:'Non-JSON response', status:200)
30
31
  end
31
32
 
32
- def api_server_error(type = :json)
33
- body = '500 Server Error'
33
+ def api_error(type: :json, body: 'Not Found', status: 404, url: 'example.com/api')
34
+ body = "#{status} #{body}"
35
+ body = { 'error' => body } if type == :json
34
36
 
35
- body = { 'error' => body }.to_json if type == :json
36
-
37
- @rspec.stub_request(:any, 'example.com/api')
38
- .to_return response(body:body, status:500)
39
- end
40
-
41
- def api_client_error(type = :json)
42
- body = '404 Not Found'
43
-
44
- body = { 'error' => body }.to_json if type == :json
45
-
46
- @rspec.stub_request(:any, 'example.com/api')
47
- .to_return defaults.merge(body:body, status:404)
37
+ @rspec.stub_request(:get, url)
38
+ .to_return response(body:body, status:status.to_i)
48
39
  end
49
40
 
50
41
  def api_description
@@ -57,6 +48,11 @@ class HttpStubs
57
48
  .to_return response(body:{ 'error' => '404 Not Found' }, status:404)
58
49
  end
59
50
 
51
+ def invalid_unknown_instance
52
+ @rspec.stub_request(:get, 'example.com/api/my_temp_resources/1')
53
+ .to_return response(body:'Unparseable Not Found', status:404)
54
+ end
55
+
60
56
  def known_instance
61
57
  @rspec.stub_request(:get, 'example.com/api/my_temp_resources/1')
62
58
  .to_return response(body:{ 'name' => 'Resource Name' })
@@ -68,13 +64,46 @@ class HttpStubs
68
64
  body: {
69
65
  '_embedded' => {
70
66
  'my_temp_resources' => [
71
- { 'name' => 'Resource Name' }
67
+ persisted_resource
72
68
  ]
73
69
  }
74
70
  }
75
71
  )
76
72
  end
77
73
 
74
+ def filtered_resource
75
+ @rspec.stub_request(:get, 'example.com/api/my_filtered_resources/filters/bar')
76
+ .to_return response(
77
+ body: {
78
+ '_embedded' => {
79
+ 'my_filtered_resources' => [
80
+ persisted_resource
81
+ ]
82
+ }
83
+ }
84
+ )
85
+ end
86
+
87
+ def valid_created_resource
88
+ @rspec.stub_request(:post, 'example.com/api/my_temp_resources/')
89
+ .to_return response(
90
+ body: persisted_resource
91
+ )
92
+ end
93
+
94
+ def invalid_created_resource
95
+ @rspec.stub_request(:post, 'example.com/api/my_temp_resources/')
96
+ .to_return response(
97
+ status: 422,
98
+ body: persisted_resource.merge(
99
+ 'errors' => {
100
+ 'name' => ['cannot be blank', 'must be a name'],
101
+ 'base' => ['cannot be valid']
102
+ }
103
+ )
104
+ )
105
+ end
106
+
78
107
  # rubocop:disable Metrics/MethodLength
79
108
  def schema
80
109
  {
@@ -87,6 +116,13 @@ class HttpStubs
87
116
  name: 'string'
88
117
  }
89
118
  },
119
+ my_filtered_resource: {
120
+ description: 'Humanized resource description',
121
+ properties: {
122
+ id: 'number',
123
+ name: 'string'
124
+ }
125
+ },
90
126
  abstract_resource: {
91
127
  description: 'A random thing',
92
128
  properties: {
@@ -103,6 +139,10 @@ class HttpStubs
103
139
  my_temp_resources: {
104
140
  href: '/api/my_temp_resources'
105
141
  },
142
+ my_filtered_resources: {
143
+ href: '/api/my_filtered_resources/filters/{filter}',
144
+ templated: true
145
+ },
106
146
  my_temp_resource: {
107
147
  href: '/api/my_temp_resources/{id}',
108
148
  templated: true
@@ -114,7 +154,21 @@ class HttpStubs
114
154
  }
115
155
  end
116
156
  # rubocop:enable Metrics/MethodLength
157
+
158
+ def persisted_resource(params = {})
159
+ id = params.fetch('id', 1)
160
+ {
161
+ 'id' => id,
162
+ 'name' => 'Resource Name',
163
+ '_links' => {
164
+ 'self' => {
165
+ 'href' => "/api/my_temp_resources/#{id}"
166
+ }
167
+ }
168
+ }.merge!(params)
169
+ end
117
170
  end
171
+ # rubocop:enable Metrics/ClassLength
118
172
 
119
173
  RSpec.configure do |c|
120
174
  c.before :all do
@@ -54,190 +54,176 @@ describe Frenetic do
54
54
  end
55
55
  end
56
56
 
57
- # describe '#connection' do
58
- # subject { super().connection }
59
-
60
- # it 'returns a Faraday Connection' do
61
- # expect(subject).to be_a Faraday::Connection
62
- # end
63
-
64
- # context 'when Frenetic is initialized with a block' do
65
- # subject do
66
- # builder = nil
67
- # described_class.new(test_cfg) do |b|
68
- # builder = b
69
- # end.connection
70
- # builder
71
- # end
72
-
73
- # it 'yields the Faraday builder to the block argument' do
74
- # expect(subject).to be_a Faraday::Connection
75
- # end
76
- # end
77
-
78
- # describe 'middleware' do
79
- # subject { super().builder.handlers }
80
-
81
- # context 'configured with a :username' do
82
- # let(:test_cfg) { super().merge username:'foo' }
83
-
84
- # it 'includes Basic Auth middleware' do
85
- # expect(subject).to include Faraday::Request::BasicAuthentication
86
- # end
87
- # end
88
-
89
- # context 'configured with a :api_token' do
90
- # let(:test_cfg) { super().merge api_token:'API_TOKEN' }
91
-
92
- # it 'includes Token Authentication middleware' do
93
- # expect(subject).to include Faraday::Request::TokenAuthentication
94
- # end
95
- # end
96
-
97
- # context 'configured to use Rack::Cache' do
98
- # let(:test_cfg) { super().merge cache: :rack }
99
-
100
- # it 'includes Rack middleware' do
101
- # expect(subject).to include FaradayMiddleware::RackCompatible
102
- # end
103
- # end
104
- # end
105
- # end
106
-
107
- # describe '#description' do
108
- # subject { super().description }
109
-
110
- # context 'with a URL that returns a' do
111
- # context 'valid response' do
112
- # before { @stubs.api_description }
113
-
114
- # it 'includes meta Hypermedia properties' do
115
- # expect(subject).to include '_embedded'
116
- # expect(subject).to include '_links'
117
- # end
118
- # end
119
-
120
- # context 'server error' do
121
- # before { @stubs.api_server_error }
122
-
123
- # it 'raises an error' do
124
- # expect{subject}.to raise_error Frenetic::ServerParsingError
125
- # end
126
- # end
127
-
128
- # context 'client error' do
129
- # before { @stubs.api_client_error :json }
130
-
131
- # it 'raises an error' do
132
- # expect{subject}.to raise_error Frenetic::ClientError
133
- # end
134
- # end
135
-
136
- # context 'JSON parsing error' do
137
- # context 'for an otherwise successful response' do
138
- # before { @stubs.api_html_response }
139
-
140
- # it 'raises an error' do
141
- # expect{subject}.to raise_error Frenetic::UnknownParsingError
142
- # end
143
- # end
144
-
145
- # context 'for a server error' do
146
- # before { @stubs.api_server_error :text }
147
-
148
- # it 'raises an error' do
149
- # expect{subject}.to raise_error Frenetic::ServerParsingError
150
- # end
151
- # end
152
-
153
- # context 'for a client error' do
154
- # before { @stubs.api_client_error :text }
155
-
156
- # it 'raises an error' do
157
- # expect{subject}.to raise_error Frenetic::ClientParsingError
158
- # end
159
- # end
160
- # end
161
- # end
162
- # end
163
-
164
- # describe '.schema' do
165
- # before { @stubs.api_description }
166
-
167
- # subject { super().schema }
168
-
169
- # it 'includes a list of defined resources' do
170
- # expect(subject).to include 'my_temp_resource'
171
- # end
172
- # end
173
-
174
- # describe '#get' do
175
- # subject { super().get '/foo' }
176
-
177
- # it 'delegates to Faraday' do
178
- # allow(instance.connection).to receive(:get)
179
- # subject
180
- # expect(instance.connection).to have_received(:get)
181
- # end
182
- # end
183
-
184
- # describe '#put' do
185
- # subject { super().put '/foo' }
186
-
187
- # it 'delegates to Faraday' do
188
- # allow(instance.connection).to receive(:put)
189
- # subject
190
- # expect(instance.connection).to have_received(:put)
191
- # end
192
- # end
193
-
194
- # describe '#patch' do
195
- # subject { super().patch '/foo' }
196
-
197
- # it 'delegates to Faraday' do
198
- # allow(instance.connection).to receive(:patch)
199
- # subject
200
- # expect(instance.connection).to have_received(:patch)
201
- # end
202
- # end
203
-
204
- # describe '#head' do
205
- # subject { instance.head '/foo' }
206
-
207
- # it 'delegates to Faraday' do
208
- # allow(instance.connection).to receive(:head)
209
- # subject
210
- # expect(instance.connection).to have_received(:head)
211
- # end
212
- # end
213
-
214
- # describe '#options' do
215
- # subject { instance.options '/foo' }
216
-
217
- # it 'delegates to Faraday' do
218
- # allow(instance.connection).to receive(:options)
219
- # subject
220
- # expect(instance.connection).to have_received(:options)
221
- # end
222
- # end
223
-
224
- # describe '#post' do
225
- # subject { super().post '/foo' }
226
-
227
- # it 'delegates to Faraday' do
228
- # allow(instance.connection).to receive(:post)
229
- # subject
230
- # expect(instance.connection).to have_received(:post)
231
- # end
232
- # end
233
-
234
- # describe '#delete' do
235
- # subject { super().delete '/foo' }
236
-
237
- # it 'delegates to Faraday' do
238
- # allow(instance.connection).to receive(:delete)
239
- # subject
240
- # expect(instance.connection).to have_received(:delete)
241
- # end
242
- # end
57
+ describe '#connection' do
58
+ subject { super().connection }
59
+
60
+ it 'returns a Frenetic Connection instance' do
61
+ expect(subject).to be_a Frenetic::Connection
62
+ end
63
+
64
+ describe 'middleware' do
65
+ subject { super().builder.handlers }
66
+
67
+ context 'configured with a :username' do
68
+ let(:test_cfg) { super().merge username:'foo' }
69
+
70
+ it 'includes Basic Auth middleware' do
71
+ expect(subject).to include Faraday::Request::BasicAuthentication
72
+ end
73
+ end
74
+
75
+ context 'configured with a :api_token' do
76
+ let(:test_cfg) { super().merge api_token:'API_TOKEN' }
77
+
78
+ it 'includes Token Authentication middleware' do
79
+ expect(subject).to include Faraday::Request::TokenAuthentication
80
+ end
81
+ end
82
+
83
+ context 'configured to use Rack::Cache' do
84
+ let(:test_cfg) { super().merge cache: :rack }
85
+
86
+ it 'includes Rack middleware' do
87
+ expect(subject).to include FaradayMiddleware::RackCompatible
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#description' do
94
+ subject { super().description }
95
+
96
+ context 'with a URL that returns a' do
97
+ context 'valid response' do
98
+ before { @stubs.api_description }
99
+
100
+ it 'includes meta Hypermedia properties' do
101
+ expect(subject).to include '_embedded'
102
+ expect(subject).to include '_links'
103
+ end
104
+ end
105
+
106
+ context 'server error' do
107
+ before { @stubs.api_error(type: :text, status: 500) }
108
+
109
+ it 'raises an error' do
110
+ expect{subject}.to raise_error Frenetic::ServerParsingError
111
+ end
112
+ end
113
+
114
+ context 'client error' do
115
+ before { @stubs.api_error }
116
+
117
+ it 'raises an error' do
118
+ expect{subject}.to raise_error Frenetic::ClientError
119
+ end
120
+ end
121
+
122
+ context 'JSON parsing error' do
123
+ context 'for an otherwise successful response' do
124
+ before { @stubs.api_html_response }
125
+
126
+ it 'raises an error' do
127
+ expect{subject}.to raise_error Frenetic::UnknownParsingError
128
+ end
129
+ end
130
+
131
+ context 'for a server error' do
132
+ before { @stubs.api_error(type: :text, status: 500) }
133
+
134
+ it 'raises an error' do
135
+ expect{subject}.to raise_error Frenetic::ServerParsingError
136
+ end
137
+ end
138
+
139
+ context 'for a client error' do
140
+ before { @stubs.api_error(type: :text) }
141
+
142
+ it 'raises an error' do
143
+ expect{subject}.to raise_error Frenetic::ClientParsingError
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '.schema' do
151
+ before { @stubs.api_description }
152
+
153
+ subject { super().schema }
154
+
155
+ it 'includes a list of defined resources' do
156
+ expect(subject).to include 'my_temp_resource'
157
+ end
158
+ end
159
+
160
+ describe '#get' do
161
+ subject { super().get '/foo' }
162
+
163
+ it 'delegates to Faraday' do
164
+ allow(instance.connection).to receive(:get)
165
+ subject
166
+ expect(instance.connection).to have_received(:get)
167
+ end
168
+ end
169
+
170
+ describe '#put' do
171
+ subject { super().put '/foo' }
172
+
173
+ it 'delegates to Faraday' do
174
+ allow(instance.connection).to receive(:put)
175
+ subject
176
+ expect(instance.connection).to have_received(:put)
177
+ end
178
+ end
179
+
180
+ describe '#patch' do
181
+ subject { super().patch '/foo' }
182
+
183
+ it 'delegates to Faraday' do
184
+ allow(instance.connection).to receive(:patch)
185
+ subject
186
+ expect(instance.connection).to have_received(:patch)
187
+ end
188
+ end
189
+
190
+ describe '#head' do
191
+ subject { instance.head '/foo' }
192
+
193
+ it 'delegates to Faraday' do
194
+ allow(instance.connection).to receive(:head)
195
+ subject
196
+ expect(instance.connection).to have_received(:head)
197
+ end
198
+ end
199
+
200
+ describe '#options' do
201
+ subject { instance.options '/foo' }
202
+
203
+ it 'delegates to Faraday' do
204
+ allow(instance.connection).to receive(:options)
205
+ subject
206
+ expect(instance.connection).to have_received(:options)
207
+ end
208
+ end
209
+
210
+ describe '#post' do
211
+ subject { super().post '/foo' }
212
+
213
+ it 'delegates to Faraday' do
214
+ allow(instance.connection).to receive(:post)
215
+ subject
216
+ expect(instance.connection).to have_received(:post)
217
+ end
218
+ end
219
+
220
+ describe '#delete' do
221
+ subject { super().delete '/foo' }
222
+
223
+ it 'delegates to Faraday' do
224
+ allow(instance.connection).to receive(:delete)
225
+ subject
226
+ expect(instance.connection).to have_received(:delete)
227
+ end
228
+ end
243
229
  end