frenetic 0.0.20.alpha.6 → 1.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/Appraisals +9 -0
  4. data/Gemfile +1 -1
  5. data/README.md +2 -2
  6. data/frenetic.gemspec +8 -5
  7. data/gemfiles/faraday_08.gemfile +10 -0
  8. data/gemfiles/faraday_08.gemfile.lock +77 -0
  9. data/gemfiles/faraday_09.gemfile +10 -0
  10. data/gemfiles/faraday_09.gemfile.lock +77 -0
  11. data/lib/frenetic.rb +57 -30
  12. data/lib/frenetic/briefly_memoizable.rb +34 -0
  13. data/lib/frenetic/concerns/collection_rest_methods.rb +1 -1
  14. data/lib/frenetic/concerns/hal_linked.rb +5 -35
  15. data/lib/frenetic/concerns/member_rest_methods.rb +0 -2
  16. data/lib/frenetic/concerns/structured.rb +0 -5
  17. data/lib/frenetic/connection.rb +110 -0
  18. data/lib/frenetic/errors.rb +112 -0
  19. data/lib/frenetic/hypermedia_link.rb +74 -0
  20. data/lib/frenetic/hypermedia_link_set.rb +43 -0
  21. data/lib/frenetic/middleware/hal_json.rb +9 -12
  22. data/lib/frenetic/resource.rb +22 -6
  23. data/lib/frenetic/resource_collection.rb +0 -1
  24. data/lib/frenetic/resource_mockery.rb +55 -1
  25. data/lib/frenetic/version.rb +1 -1
  26. data/spec/{concerns/breifly_memoizable_spec.rb → briefly_memoizable_spec.rb} +10 -18
  27. data/spec/concerns/hal_linked_spec.rb +49 -62
  28. data/spec/concerns/member_rest_methods_spec.rb +8 -10
  29. data/spec/concerns/structured_spec.rb +70 -75
  30. data/spec/connection_spec.rb +137 -0
  31. data/spec/fixtures/test_api_requests.rb +8 -2
  32. data/spec/frenetic_spec.rb +221 -133
  33. data/spec/hypermedia_link_set_spec.rb +155 -0
  34. data/spec/hypermedia_link_spec.rb +153 -0
  35. data/spec/middleware/hal_json_spec.rb +13 -15
  36. data/spec/resource_collection_spec.rb +17 -16
  37. data/spec/resource_mockery_spec.rb +69 -0
  38. data/spec/resource_spec.rb +110 -63
  39. data/spec/support/rspec.rb +0 -1
  40. metadata +88 -75
  41. data/lib/frenetic/concerns/briefly_memoizable.rb +0 -34
  42. data/lib/frenetic/concerns/configurable.rb +0 -59
  43. data/lib/frenetic/concerns/resource_mockery.rb +0 -48
  44. data/lib/frenetic/configuration.rb +0 -88
  45. data/spec/concerns/configurable_spec.rb +0 -50
  46. data/spec/concerns/resource_mockery_spec.rb +0 -56
  47. data/spec/configuration_spec.rb +0 -134
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::HypermediaLinkSet do
4
+ subject(:instance) { described_class.new links }
5
+
6
+ describe '#initialize' do
7
+ context 'with a single link' do
8
+ let(:links) { { href:'foo' } }
9
+
10
+ it 'converts the argument to an array' do
11
+ expect(subject.size).to eq 1
12
+ end
13
+
14
+ it 'transforms it into HypermediaLink' do
15
+ expect(subject.first).to be_an_instance_of Frenetic::HypermediaLink
16
+ end
17
+ end
18
+ end
19
+
20
+ describe '#href' do
21
+ subject { super().href tmpl_data }
22
+
23
+ context 'without template data' do
24
+ let(:tmpl_data) {}
25
+
26
+ context 'with a single link in the set' do
27
+ let(:link_a) do
28
+ Frenetic::HypermediaLink.new href:'/foo/bar'
29
+ end
30
+
31
+ let(:links) { [link_a] }
32
+
33
+ it 'returns the HREF of the first link' do
34
+ allow(link_a).to receive(:href).and_call_original
35
+ expect(subject).to eq '/foo/bar'
36
+ expect(link_a).to have_received(:href)
37
+ end
38
+ end
39
+
40
+ context 'with multiple links in the set' do
41
+ let(:links) do
42
+ [
43
+ { href:'/foo/bar' },
44
+ { href:'/baz/qux' }
45
+ ]
46
+ end
47
+
48
+ it 'returns the first link in the set' do
49
+ expect(subject).to eq '/foo/bar'
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'with template data' do
55
+ let(:tmpl_data) { { id:1 } }
56
+
57
+ let(:link_a) do
58
+ Frenetic::HypermediaLink.new href:'/foo/{id}', templated:true
59
+ end
60
+
61
+ let(:links) { [link_a] }
62
+
63
+ it 'finds most relevant link' do
64
+ allow(instance).to receive(:find_relevant_link)
65
+ subject
66
+ expect(instance).to have_received(:find_relevant_link)
67
+ end
68
+
69
+ it 'passes along the template data' do
70
+ allow(link_a).to receive(:href).and_call_original
71
+ subject
72
+ expect(link_a).to have_received(:href).with(tmpl_data)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#find_relevant_link' do
78
+ let(:tmpl_data) { { id:1 } }
79
+
80
+ subject { super().find_relevant_link tmpl_data }
81
+
82
+ context 'with a single, matching link' do
83
+ let(:links) do
84
+ [
85
+ { href:'/foo/{id}', templated:true }
86
+ ]
87
+ end
88
+
89
+ it 'returns the matching link' do
90
+ expect(subject).to be_an_instance_of Frenetic::HypermediaLink
91
+ end
92
+ end
93
+
94
+ context 'with multiple matching links' do
95
+ let(:link_a) do
96
+ Frenetic::HypermediaLink.new( href:'/foo/{id}', templated:true )
97
+ end
98
+
99
+ let(:links) do
100
+ [
101
+ link_a,
102
+ { href:'/bar/{id}', templated:true }
103
+ ]
104
+ end
105
+
106
+ it 'returns the first matching link' do
107
+ expect(subject).to eq link_a
108
+ end
109
+ end
110
+
111
+ context 'with no matching links' do
112
+ let(:links) do
113
+ [
114
+ { href:'/foo/{fname}', templated:true },
115
+ { href:'/foo/{lname}', templated:true }
116
+ ]
117
+ end
118
+
119
+ it 'raises an error' do
120
+ expect{ subject }.to raise_error Frenetic::HypermediaError
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#[]' do
126
+ subject { super()[rel] }
127
+
128
+ let(:link_b) do
129
+ Frenetic::HypermediaLink.new( href:'/bar', rel:'bar' )
130
+ end
131
+
132
+ let(:links) do
133
+ [
134
+ { href:'/foo', rel:'foo' },
135
+ link_b
136
+ ]
137
+ end
138
+
139
+ context 'for a relation that exists' do
140
+ let(:rel) { :bar }
141
+
142
+ it 'returns the desired link' do
143
+ expect(subject).to eq link_b
144
+ end
145
+ end
146
+
147
+ context 'for a relation that does not exist' do
148
+ let(:rel) { :baz }
149
+
150
+ it 'returns nothing' do
151
+ expect(subject).to be_nil
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::HypermediaLink do
4
+ let(:basic_link) do
5
+ { href:'/api/my_link' }
6
+ end
7
+
8
+ let(:templated_link) do
9
+ { href:'/api/my_link/{id}-{title}', templated:true }
10
+ end
11
+
12
+ let(:relation_link) do
13
+ { href:'/api/foo', rel:'foo' }
14
+ end
15
+
16
+ subject { described_class.new(link) }
17
+
18
+ describe '#href' do
19
+ subject { super().href data }
20
+
21
+ context 'with a non-templated link' do
22
+ let(:link) { basic_link }
23
+ let(:data) {}
24
+
25
+ it 'correctly transforms the data' do
26
+ expect(subject).to eq '/api/my_link'
27
+ end
28
+ end
29
+
30
+ context 'with a templated link' do
31
+ let(:link) { templated_link }
32
+
33
+ context 'and complate template data' do
34
+ let(:data) { { id:1, title:'title' } }
35
+
36
+ it 'correctly transforms the data' do
37
+ expect(subject).to eq '/api/my_link/1-title'
38
+ end
39
+ end
40
+
41
+ context 'and incomplete template data' do
42
+ let(:data) { { id:1 } }
43
+
44
+ it 'raises an error' do
45
+ expect{subject}.to raise_error Frenetic::HypermediaError
46
+ end
47
+ end
48
+
49
+ context 'and non-Hash template data' do
50
+ let(:templated_link) do
51
+ { href:'/api/my_link/{id}', templated:true }
52
+ end
53
+
54
+ let(:data) { 1 }
55
+
56
+ it 'infers the template data' do
57
+ expect(subject).to eq '/api/my_link/1'
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#templated?' do
64
+ subject { super().templated? }
65
+
66
+ context 'with a non-templated link' do
67
+ let(:link) { basic_link }
68
+
69
+ it 'returns FALSE' do
70
+ expect(subject).to eq false
71
+ end
72
+ end
73
+
74
+ context 'with a templated link' do
75
+ let(:link) { templated_link }
76
+
77
+ it 'returns TRUE' do
78
+ expect(subject).to eq true
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#expandable?' do
84
+ subject { super().expandable? data }
85
+
86
+ let(:link) { templated_link }
87
+
88
+ context 'when the data can fully fulfill the template requirements' do
89
+ let(:data) { { id:1, title:'title' } }
90
+
91
+ it 'returns TRUE' do
92
+ expect(subject).to eq true
93
+ end
94
+ end
95
+
96
+ context 'when the data cannot fully fulfill the template requirements' do
97
+ let(:data) { { id:1 } }
98
+
99
+ it 'returns FALSE' do
100
+ expect(subject).to eq false
101
+ end
102
+ end
103
+
104
+ context 'when the data provides more than the template requires' do
105
+ let(:data) { { id:1, title:'title', garbage:true } }
106
+
107
+ it 'returns TRUE' do
108
+ expect(subject).to eq true
109
+ end
110
+ end
111
+
112
+ context 'with a non-template URL' do
113
+ let(:link) { basic_link }
114
+
115
+ let(:data) { { id:1 } }
116
+
117
+ it 'returns FALSE' do
118
+ expect(subject).to eq false
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '#template' do
124
+ subject { super().template }
125
+
126
+ let(:link) { templated_link }
127
+
128
+ it 'returns an Addressable Template' do
129
+ expect(subject).to be_an_instance_of Addressable::Template
130
+ end
131
+ end
132
+
133
+ describe '#as_json' do
134
+ subject { super().as_json }
135
+
136
+ let(:link) { templated_link }
137
+
138
+ it 'returns a Hash representation of the link' do
139
+ expect(subject).to include 'href' => '/api/my_link/{id}-{title}'
140
+ expect(subject).to include 'templated' => true
141
+ end
142
+ end
143
+
144
+ describe '#rel' do
145
+ subject { super().rel }
146
+
147
+ let(:link) { relation_link }
148
+
149
+ it 'returns the relation name' do
150
+ expect(subject).to eq 'foo'
151
+ end
152
+ end
153
+ end
@@ -1,13 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Frenetic::Middleware::HalJson do
4
-
5
4
  def process(body, content_type = nil, options = {}, status = 200)
6
5
  env = {
7
- body: body,
8
- request: options,
9
- response_headers: Faraday::Utils::Headers.new( headers ),
10
- status: status
6
+ body: body,
7
+ request: options,
8
+ response_headers: Faraday::Utils::Headers.new(headers),
9
+ status: status
11
10
  }
12
11
  env[:response_headers]['content-type'] = content_type if content_type
13
12
 
@@ -15,9 +14,7 @@ describe Frenetic::Middleware::HalJson do
15
14
  end
16
15
 
17
16
  let(:options) { Hash.new }
18
-
19
17
  let(:headers) { Hash.new }
20
-
21
18
  let(:middleware) do
22
19
  described_class.new(lambda {|env|
23
20
  Faraday::Response.new(env)
@@ -51,7 +48,7 @@ describe Frenetic::Middleware::HalJson do
51
48
 
52
49
  subject { process(body) }
53
50
 
54
- it 'should parse the body' do
51
+ it 'parses the body' do
55
52
  expect(subject.body).to include 'name' => 'My Name'
56
53
  end
57
54
  end
@@ -61,23 +58,24 @@ describe Frenetic::Middleware::HalJson do
61
58
 
62
59
  context 'from the server' do
63
60
  let(:status) { 500 }
61
+ let(:error) do
62
+ { 'status' => status.to_s, error:'500 Server Error' }.to_json
63
+ end
64
64
 
65
- let(:error) { { 'status' => status.to_s, error:'500 Server Error' }.to_json }
66
-
67
- it 'should raise an error' do
65
+ it 'raises an error' do
68
66
  expect{ subject }.to raise_error Frenetic::ServerError
69
67
  end
70
68
  end
71
69
 
72
70
  context 'cause by the client' do
73
71
  let(:status) { 404 }
72
+ let(:error) do
73
+ { 'status' => status.to_s, error:'404 Not Found' }.to_json
74
+ end
74
75
 
75
- let(:error) { { 'status' => status.to_s, error:'404 Not Found' }.to_json }
76
-
77
- it 'should raise an error' do
76
+ it 'raises an error' do
78
77
  expect{ subject }.to raise_error Frenetic::ClientError
79
78
  end
80
79
  end
81
80
  end
82
-
83
81
  end
@@ -43,34 +43,36 @@ describe Frenetic::ResourceCollection do
43
43
 
44
44
  subject(:instance) { described_class.new(MyTempResource, collection_response) }
45
45
 
46
- it 'should know where the resources are located' do
47
- subject.collection_key.should == 'my_temp_resources'
46
+ it 'knows where the resources are located' do
47
+ expect(subject.collection_key).to eq 'my_temp_resources'
48
48
  end
49
49
 
50
- it 'should know which resource it represents' do
51
- subject.resource_type.should == 'my_temp_resource'
50
+ it 'knows which resource it represents' do
51
+ expect(subject.resource_type).to eq 'my_temp_resource'
52
52
  end
53
53
 
54
- it 'should extract the embedded resources' do
55
- subject.size.should == 2
54
+ it 'extracts the embedded resources' do
55
+ expect(subject.size).to eq 2
56
56
  end
57
57
 
58
- it 'should parse the embedded resources' do
59
- subject.first.should be_a MyTempResource
58
+ it 'parses the embedded resources' do
59
+ expect(subject.first).to be_a MyTempResource
60
60
  end
61
61
 
62
- it 'should be able to make API calls' do
63
- subject.api.should be_an_instance_of Frenetic
62
+ it 'is able to make API calls' do
63
+ expect(subject.api).to be_an_instance_of Frenetic
64
64
  end
65
65
 
66
- it 'should have links' do
67
- subject.links.should_not be_empty
66
+ it 'has links' do
67
+ expect(subject.links).to_not be_empty
68
68
  end
69
69
 
70
70
  context 'for a non-embedded resource' do
71
71
  subject { described_class.new(MyTempResource) }
72
72
 
73
- it { should be_empty }
73
+ it 'is empty' do
74
+ expect(subject).to be_empty
75
+ end
74
76
  end
75
77
 
76
78
  describe '#get' do
@@ -78,9 +80,8 @@ describe Frenetic::ResourceCollection do
78
80
 
79
81
  subject { super().get(1) }
80
82
 
81
- it 'should GET the full representation' do
82
- subject.should be_an_instance_of MyTempResource
83
+ it 'issues a GET the full representation' do
84
+ expect(subject).to be_an_instance_of MyTempResource
83
85
  end
84
86
  end
85
-
86
87
  end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ require 'frenetic/resource_mockery'
4
+
5
+ describe Frenetic::ResourceMockery do
6
+ let(:my_temp_resource) do
7
+ Class.new(Frenetic::Resource)
8
+ end
9
+
10
+ let(:my_mocked_resource) do
11
+ Class.new(my_temp_resource) do
12
+ def self.default_attributes
13
+ { qux:'qux' }
14
+ end
15
+ end
16
+ end
17
+
18
+ before do
19
+ stub_const 'MyNamespace::MyMockedResource', my_mocked_resource
20
+ MyNamespace::MyMockedResource.send :include, described_class
21
+ end
22
+
23
+ let(:params) { { foo:1, bar:'baz' } }
24
+
25
+ subject { MyNamespace::MyMockedResource.new params }
26
+
27
+ it 'violates some basic CS principles by telling the parent-class of its existence' do
28
+ expect(my_temp_resource.instance_variables).to include :@mock_class
29
+ end
30
+
31
+ describe '#properties' do
32
+ subject { super().properties }
33
+
34
+ it 'returns a hash of available properties' do
35
+ expect(subject).to include 'foo' => 'fixnum'
36
+ expect(subject).to include 'bar' => 'string'
37
+ end
38
+ end
39
+
40
+ describe '#attributes' do
41
+ subject { super().attributes }
42
+
43
+ it 'returns a hash of the resources attributes' do
44
+ expect(subject).to include 'foo' => 1
45
+ expect(subject).to include 'bar' => 'baz'
46
+ expect(subject).to include 'qux' => 'qux'
47
+ end
48
+ end
49
+
50
+ describe '.default_attributes' do
51
+ let(:my_mocked_resource) { Class.new(my_temp_resource) }
52
+
53
+ subject { MyNamespace::MyMockedResource.default_attributes }
54
+
55
+ it 'allows implementors to specify sane defaults' do
56
+ expect(subject).to eq Hash.new
57
+ end
58
+ end
59
+
60
+ describe '#default_attributes' do
61
+ let(:my_mocked_resource) { Class.new(my_temp_resource) }
62
+
63
+ subject { MyNamespace::MyMockedResource.new.default_attributes }
64
+
65
+ it 'proxies to the class method' do
66
+ expect(subject).to eq Hash.new
67
+ end
68
+ end
69
+ end