frenetic 2.0.0 → 3.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.
@@ -1,3 +1,3 @@
1
1
  class Frenetic
2
- VERSION = '2.0.0'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::Related do
4
+ let(:success) { true }
5
+
6
+ let(:response) do
7
+ res = Struct.new(:body, :success?)
8
+ res.new({
9
+ 'id' => 99,
10
+ 'foo' => 'bar'
11
+ }, success)
12
+ end
13
+
14
+ let(:api_stub) { double('ApiDouble', get: response).as_null_object }
15
+
16
+ let(:related_resource) do
17
+ Class.new do
18
+ attr_reader :id
19
+
20
+ def initialize(attrs = {})
21
+ @id = attrs.fetch('id')
22
+ end
23
+ end
24
+ end
25
+
26
+ let(:my_temp_resource) do
27
+ Class.new do
28
+ def initialize(attrs = {})
29
+ @attrs = attrs
30
+ end
31
+
32
+ def api
33
+ self.class.class_variable_get('@@_api_stub')
34
+ end
35
+
36
+ def links
37
+ @attrs['_links']
38
+ end
39
+
40
+ def self.find_resource_class(*args)
41
+ RelatedResource
42
+ end
43
+ end
44
+ end
45
+
46
+ let(:params) do
47
+ {
48
+ '_links' => {
49
+ 'self' => {},
50
+ 'related_resource' => {
51
+ 'href' => '/related_resource'
52
+ }
53
+ }
54
+ }
55
+ end
56
+
57
+ before do
58
+ stub_const('MyTempResource', my_temp_resource)
59
+ stub_const('RelatedResource', related_resource)
60
+ MyTempResource.class_variable_set('@@_api_stub', api_stub)
61
+ MyTempResource.send(:include, described_class)
62
+ end
63
+
64
+ subject(:instance) { MyTempResource.new(params) }
65
+
66
+ describe '#extract_related_resources' do
67
+ subject { super().extract_related_resources }
68
+
69
+ it 'builds a relation map' do
70
+ subject
71
+ expect(instance.send(:relations)).to_not be_empty
72
+ end
73
+
74
+ it 'does not extract self-referential resources' do
75
+ subject
76
+ expect(instance.send(:relations)).to_not include 'self'
77
+ end
78
+ end
79
+
80
+ describe '#fetch_related_resource' do
81
+ let(:relation) { 'related_resource' }
82
+
83
+ let(:props) do
84
+ {
85
+ 'id' => 99
86
+ }
87
+ end
88
+
89
+ subject { super().fetch_related_resource(relation, props) }
90
+
91
+ it 'returns the instantiated related resource instance' do
92
+ expect(subject).to be_a RelatedResource
93
+ expect(subject.id).to eql 99
94
+ end
95
+
96
+ context 'for an unsuccessul resource request' do
97
+ let(:success) { false }
98
+
99
+ it 'returns nil' do
100
+ expect(subject).to be_nil
101
+ end
102
+ end
103
+
104
+ context 'with an unknown resource instance' do
105
+ let(:exception) do
106
+ Frenetic::ClientError.new(status: 404)
107
+ end
108
+
109
+ before do
110
+ allow(api_stub).to receive(:get).and_raise(exception)
111
+ end
112
+
113
+ it 'raises an error' do
114
+ expect{subject}.to raise_error(Frenetic::ResourceNotFound)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::StructureMethodDefiner do
4
+ let(:my_temp_resource) { Class.new }
5
+
6
+ before do
7
+ stub_const('MyTempResource', my_temp_resource)
8
+ MyTempResource.send(:extend, described_class)
9
+ end
10
+
11
+ describe '.structure' do
12
+ before do
13
+ MyTempResource.structure do
14
+ 123
15
+ end
16
+ end
17
+
18
+ subject { MyTempResource.instance_variable_get('@_structure_block') }
19
+
20
+ it 'stores a reference to the block' do
21
+ expect(subject).to_not be_nil
22
+ end
23
+ end
24
+ end
@@ -11,12 +11,12 @@ describe Frenetic::ResourceMockery do
11
11
  Class.new(my_temp_resource) do
12
12
  def self.default_attributes
13
13
  {
14
- id: 1,
15
- price: 0.0,
16
- qux: 'qux',
17
- _embedded: {
18
- embedded_resource: {
19
- plugh: 'xyzzy'
14
+ 'id' => 1,
15
+ 'price' => 0.0,
16
+ 'qux' => 'qux',
17
+ '_embedded' => {
18
+ 'embedded_resource' => {
19
+ 'plugh' => 'xyzzy'
20
20
  }
21
21
  }
22
22
  }
@@ -26,16 +26,16 @@ describe Frenetic::ResourceMockery do
26
26
 
27
27
  let(:params) do
28
28
  {
29
- id: '1',
30
- foo: 'bar',
31
- price: '0.0',
32
- bar: 'baz',
29
+ 'id' => '1',
30
+ 'foo' => 'bar',
31
+ 'price' => '0.0',
32
+ 'bar' => 'baz',
33
33
  _embedded: {
34
- embedded_resource: {
35
- grault: 'garply'
34
+ 'embedded_resource' => {
35
+ 'grault' => 'garply'
36
36
  },
37
- another_resource: {
38
- waldo: 'fred'
37
+ 'another_resource' => {
38
+ 'waldo' => 'fred'
39
39
  }
40
40
  }
41
41
  }
@@ -88,6 +88,66 @@ describe Frenetic::Resource do
88
88
  end
89
89
  end
90
90
 
91
+ describe '.extract_embedded_resources' do
92
+ let(:resource) do
93
+ {
94
+ '_embedded' => {
95
+ 'my_temp_resource' => {
96
+ 'id' => 99
97
+ }
98
+ }
99
+ }
100
+ end
101
+
102
+ before { @stubs.api_description }
103
+
104
+ subject do
105
+ MyNamespace::MyTempResource.extract_embedded_resources(resource)
106
+ end
107
+
108
+ it 'extracts all embedded resources' do
109
+ expect(subject).to include 'my_temp_resource' => kind_of(MyNamespace::MyTempResource)
110
+ end
111
+ end
112
+
113
+ describe '.find_resource_class' do
114
+ let(:params) do
115
+ {
116
+ id: 54,
117
+ _links: {
118
+ known_resource: {
119
+ href: '/known_resource'
120
+ }
121
+ }
122
+ }
123
+ end
124
+
125
+ before do
126
+ @stubs.api_description
127
+ stub_const('MyNamespace::KnownResource', abstract_resource)
128
+ end
129
+
130
+ subject do
131
+ MyNamespace::MyTempResource.find_resource_class(resource_name)
132
+ end
133
+
134
+ context 'with a known resource' do
135
+ let(:resource_name) { 'known_resource' }
136
+
137
+ it 'returns the resource class' do
138
+ expect(subject).to eql MyNamespace::KnownResource
139
+ end
140
+ end
141
+
142
+ context 'with an unknown resource' do
143
+ let(:resource_name) { 'unknown_resource' }
144
+
145
+ it 'returns an anonymous class' do
146
+ expect(subject).to eql OpenStruct
147
+ end
148
+ end
149
+ end
150
+
91
151
  describe '.namespace' do
92
152
  before do
93
153
  stub_const 'MyNamespace::MyTempResource', abstract_resource
@@ -278,9 +338,9 @@ describe Frenetic::Resource do
278
338
  expect{subject.gender}.to raise_error(NoMethodError)
279
339
  end
280
340
 
281
- it 'is accessible in @params' do
282
- params = subject.instance_variable_get('@params')
283
- expect(params).to include 'gender' => 'male'
341
+ it 'is accessible in @raw_attributes' do
342
+ raw_attrs = subject.instance_variable_get('@raw_attributes')
343
+ expect(raw_attrs).to include 'gender' => 'male'
284
344
  end
285
345
  end
286
346
 
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::StructureRegistry::Rebuilder do
4
+ let(:signatures) { {} }
5
+ let(:resource) { Class.new }
6
+ let(:attributes) do
7
+ {
8
+ 'id' => 123
9
+ }
10
+ end
11
+ let(:key) { 'MockKey' }
12
+ let(:signature) { 'abc123def456' }
13
+
14
+ subject(:instance) { described_class.new(signatures, resource, attributes, key, signature) }
15
+
16
+ after do
17
+ Struct.send(:remove_const, key) if Struct.constants.include?(key.to_sym)
18
+ end
19
+
20
+ describe '#call' do
21
+ subject { super().call }
22
+
23
+ it 'removes any existing registration' do
24
+ allow(instance).to receive(:destroy!)
25
+ subject
26
+ expect(instance).to have_received(:destroy!)
27
+ end
28
+
29
+ it 'registers the Struct signature' do
30
+ subject
31
+ expect(instance.signatures[key]).to eql signature
32
+ end
33
+
34
+ it 'returns a Struct for the given resource' do
35
+ expect(subject).to eql Struct::MockKey
36
+ end
37
+ end
38
+
39
+ describe '#destroy!' do
40
+ let!(:struct) { Struct.new(key) }
41
+
42
+ subject { super().destroy! }
43
+
44
+ it 'removes the registered signature' do
45
+ subject
46
+ expect(instance.signatures).to_not include(key)
47
+ end
48
+
49
+ it 'removes the registered Struct constant' do
50
+ subject
51
+ expect{Struct.const_get(key.to_sym)}.to raise_error(NameError, %r[uninitialized constant Struct::#{key}])
52
+ end
53
+
54
+ context 'for a non-existant struct' do
55
+ before do
56
+ allow(instance).to receive(:exists?).and_return(false)
57
+ end
58
+
59
+ it 'does nothing' do
60
+ subject
61
+ expect(Struct.const_get(key)).to_not be_nil
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#exists?' do
67
+ subject { super().exists? }
68
+
69
+ context 'for a registered resource' do
70
+ let!(:struct) { Struct.new(key) }
71
+
72
+ it 'returns TRUE' do
73
+ expect(subject).to eql true
74
+ end
75
+ end
76
+
77
+ context 'for an un-registered resource' do
78
+ it 'returns FALSE' do
79
+ expect(subject).to eql false
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::StructureRegistry::Retriever do
4
+ let(:rebuilder_class) { Frenetic::StructureRegistry::Rebuilder }
5
+ let(:signatures) { {} }
6
+ let(:resource) { Class.new }
7
+ let(:attributes) do
8
+ {
9
+ 'id' => 123
10
+ }
11
+ end
12
+ let(:key) { 'MockKey' }
13
+
14
+ subject(:instance) do
15
+ described_class.new(signatures, resource, attributes, key, rebuilder_class: rebuilder_class)
16
+ end
17
+
18
+ describe '#initialize' do
19
+ context 'with a blank :key argument' do
20
+ let(:key) { ' ' }
21
+
22
+ it 'raises an error' do
23
+ expect{subject}.to raise_error(ArgumentError, /non-blank/)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#call' do
29
+ let(:rebuilder) { double('Rebuilder') }
30
+ let(:rebuilder_class) { double('RebuilderClass', new: rebuilder) }
31
+
32
+ subject { super().call }
33
+
34
+ context 'for an expired resource registration' do
35
+ before do
36
+ allow(instance).to receive(:expired?).and_return(true)
37
+ end
38
+
39
+ it 'invokes the Rebuilder' do
40
+ allow(rebuilder).to receive(:call)
41
+ subject
42
+ expect(rebuilder).to have_received(:call)
43
+ end
44
+ end
45
+
46
+ context 'for an un-expired resource registration' do
47
+ before do
48
+ allow(instance).to receive(:expired?).and_return(false)
49
+ end
50
+
51
+ it 'fetches the resource' do
52
+ allow(instance).to receive(:fetch_structure).and_return(true)
53
+ subject
54
+ expect(instance).to have_received(:fetch_structure)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#expired?' do
60
+ let(:resource_signature) { 'mock_signature' }
61
+ let(:signatures) do
62
+ {
63
+ key => resource_signature
64
+ }
65
+ end
66
+
67
+ before do
68
+ allow(instance).to receive(:struct_signature).and_return resource_signature
69
+ end
70
+
71
+ subject { super().expired? }
72
+
73
+ context 'for an un-registered resource' do
74
+ let(:signatures) { {} }
75
+
76
+ it 'returns TRUE' do
77
+ expect(subject).to eql true
78
+ end
79
+ end
80
+
81
+ context 'for an expired resource' do
82
+ let(:signatures) do
83
+ {
84
+ key => resource_signature + 'OLD'
85
+ }
86
+ end
87
+
88
+ it 'returns TRUE' do
89
+ expect(subject).to eql true
90
+ end
91
+ end
92
+
93
+ context 'for an un-expired resource' do
94
+ it 'returns FALSE' do
95
+ expect(subject).to eql false
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#fetch_structure' do
101
+ before { Struct.new(key) }
102
+
103
+ after { Struct.send(:remove_const, key) }
104
+
105
+ subject { super().fetch_structure }
106
+
107
+ it 'returns the registered Struct class' do
108
+ expect(subject).to eql Struct::MockKey
109
+ end
110
+ end
111
+
112
+ describe '#struct_signature' do
113
+ subject { super().struct_signature }
114
+
115
+ it 'returns a unique signature for a given resource' do
116
+ expect(subject).to match(/\A[a-z0-9]{40}\z/)
117
+ end
118
+ end
119
+ end