pdc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +27 -0
  3. data/.gitignore +10 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +21 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +32 -0
  9. data/Guardfile +36 -0
  10. data/LICENSE +21 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +41 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +11 -0
  15. data/bin/setup +11 -0
  16. data/docs/.gitignore +3 -0
  17. data/docs/LICENSE_sphinx_deployment +27 -0
  18. data/docs/Makefile +179 -0
  19. data/docs/source/conf.py +257 -0
  20. data/docs/source/example.rst +10 -0
  21. data/docs/source/index.rst +23 -0
  22. data/docs/sphinx_deployment.mk +117 -0
  23. data/examples/active_attr.rb +33 -0
  24. data/examples/http_failures.rb +18 -0
  25. data/examples/local_pdc_dev.rb +15 -0
  26. data/examples/logger.rb +50 -0
  27. data/examples/pdc_curb_access_token.rb +36 -0
  28. data/examples/pdc_resource_tests.rb +173 -0
  29. data/examples/pdc_test_cache.rb +48 -0
  30. data/examples/prod_failures.rb +26 -0
  31. data/examples/prod_pdc.rb +14 -0
  32. data/lib/pdc/base.rb +14 -0
  33. data/lib/pdc/config.rb +157 -0
  34. data/lib/pdc/errors.rb +8 -0
  35. data/lib/pdc/http/errors.rb +43 -0
  36. data/lib/pdc/http/request/append_slash.rb +19 -0
  37. data/lib/pdc/http/request.rb +12 -0
  38. data/lib/pdc/http/response/pagination.rb +43 -0
  39. data/lib/pdc/http/response/parser.rb +62 -0
  40. data/lib/pdc/http/response/raise_error.rb +13 -0
  41. data/lib/pdc/http/response.rb +3 -0
  42. data/lib/pdc/http/result.rb +28 -0
  43. data/lib/pdc/http.rb +12 -0
  44. data/lib/pdc/logger.rb +19 -0
  45. data/lib/pdc/resource/attribute_modifier.rb +43 -0
  46. data/lib/pdc/resource/attribute_store.rb +22 -0
  47. data/lib/pdc/resource/attributes.rb +144 -0
  48. data/lib/pdc/resource/errors.rb +3 -0
  49. data/lib/pdc/resource/identity.rb +75 -0
  50. data/lib/pdc/resource/path.rb +63 -0
  51. data/lib/pdc/resource/per_thread_registry.rb +54 -0
  52. data/lib/pdc/resource/relation/finder.rb +24 -0
  53. data/lib/pdc/resource/relation/pagination.rb +33 -0
  54. data/lib/pdc/resource/relation/query.rb +14 -0
  55. data/lib/pdc/resource/relation.rb +81 -0
  56. data/lib/pdc/resource/rest_api.rb +34 -0
  57. data/lib/pdc/resource/scope_registry.rb +19 -0
  58. data/lib/pdc/resource/scopes.rb +29 -0
  59. data/lib/pdc/resource/value_parser.rb +31 -0
  60. data/lib/pdc/resource/wip.rb +0 -0
  61. data/lib/pdc/resource.rb +12 -0
  62. data/lib/pdc/v1/arch.rb +6 -0
  63. data/lib/pdc/v1/product.rb +5 -0
  64. data/lib/pdc/v1/release.rb +18 -0
  65. data/lib/pdc/v1/release_variant.rb +17 -0
  66. data/lib/pdc/v1.rb +8 -0
  67. data/lib/pdc/version.rb +3 -0
  68. data/lib/pdc.rb +25 -0
  69. data/pdc.gemspec +38 -0
  70. data/spec/fixtures/vcr/_page_count_returns_total_count.yml +141 -0
  71. data/spec/fixtures/vcr/brew_can_be_nil.yml +61 -0
  72. data/spec/fixtures/vcr/brew_may_be_present.yml +50 -0
  73. data/spec/fixtures/vcr/caches_multiple_response.yml +173 -0
  74. data/spec/fixtures/vcr/caches_response_with_a_query.yml +64 -0
  75. data/spec/fixtures/vcr/caches_response_with_multiple_query.yml +64 -0
  76. data/spec/fixtures/vcr/caches_response_without_query.yml +61 -0
  77. data/spec/fixtures/vcr/can_iterate_using_each.yml +187 -0
  78. data/spec/fixtures/vcr/fetches_variants_of_a_release.yml +663 -0
  79. data/spec/fixtures/vcr/must_return_number_of_resources.yml +61 -0
  80. data/spec/fixtures/vcr/preserves_the_filters.yml +49 -0
  81. data/spec/fixtures/vcr/returns_resources_on_that_page.yml +49 -0
  82. data/spec/fixtures/vcr/returns_the_total_count_and_not_items_in_page.yml +135 -0
  83. data/spec/fixtures/vcr/should_not_be_in_the_list_of_attributes.yml +95 -0
  84. data/spec/fixtures/vcr/works_with_where.yml +49 -0
  85. data/spec/pdc/config_spec.rb +115 -0
  86. data/spec/pdc/http/errors_spec.rb +58 -0
  87. data/spec/pdc/resource/attributes_spec.rb +231 -0
  88. data/spec/pdc/resource/cache_spec.rb +88 -0
  89. data/spec/pdc/resource/count_spec.rb +45 -0
  90. data/spec/pdc/resource/identity_spec.rb +96 -0
  91. data/spec/pdc/resource/pagination_spec.rb +51 -0
  92. data/spec/pdc/resource/path_spec.rb +30 -0
  93. data/spec/pdc/resource/relation_spec.rb +94 -0
  94. data/spec/pdc/resource/rest_api_spec.rb +23 -0
  95. data/spec/pdc/resource/value_parser_spec.rb +15 -0
  96. data/spec/pdc/resource/wip_spec.rb +0 -0
  97. data/spec/pdc/v1/arch_spec.rb +34 -0
  98. data/spec/pdc/v1/release_spec.rb +54 -0
  99. data/spec/pdc/version_spec.rb +5 -0
  100. data/spec/spec_helper.rb +39 -0
  101. data/spec/support/fixtures.rb +116 -0
  102. data/spec/support/vcr.rb +10 -0
  103. data/spec/support/webmock.rb +34 -0
  104. metadata +295 -0
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ describe PDC::Resource::AttributeStore do
4
+ subject { PDC::Resource::AttributeStore }
5
+
6
+ it '#to_params' do
7
+ attr = PDC::Resource::AttributeStore.new(
8
+ id: 3, 'title' => 'Fish',
9
+ groups: [
10
+ { name: 'Dessert' }
11
+ ]
12
+ )
13
+ attr.to_params.must_equal(
14
+ 'id' => 3,
15
+ 'title' => 'Fish',
16
+ 'groups' => [{ 'name' => 'Dessert' }]
17
+ )
18
+ end
19
+ end
20
+
21
+ describe PDC::Resource::Attributes do
22
+ it 'returns all attributes' do
23
+ Product.attributes.wont_be_empty
24
+ end
25
+
26
+ it 'primary_key will be in attributes by default' do
27
+ Product.attributes.must_include Product.primary_key
28
+ end
29
+
30
+ it 'allows block initialization' do
31
+ prod = Product.new do |p|
32
+ p.name = 'RHEL'
33
+ p.description = 'Enterprise Linux'
34
+ end
35
+ assert_equal 'RHEL', prod.name
36
+ assert_equal 'Enterprise Linux', prod.description
37
+ end
38
+
39
+ it 'can handle predicate' do
40
+ stub_get('products/1').to_return_json(data: [{ id: 1, name: 'RHEL' }])
41
+ product = Product.find(1)
42
+ assert_equal true, product.name?
43
+ assert_equal false, product.description?
44
+ end
45
+
46
+ it 'can assign a hash' do
47
+ product = Product.new(id: 2)
48
+ product.attributes = { name: 'RHEL' }
49
+
50
+ product.name.must_equal 'RHEL'
51
+ product.id.must_equal 2
52
+ end
53
+
54
+ it 'can get and set value' do
55
+ product = Product.new
56
+ product.name = 'RHEL'
57
+ product.name.must_equal 'RHEL'
58
+ end
59
+
60
+ it 'works with []' do
61
+ product = Product.new(name: 'Fedora')
62
+ product[:name].must_equal 'Fedora'
63
+ end
64
+
65
+ it 'works with []=' do
66
+ product = Product.new
67
+ product[:name] = 'bar'
68
+ product.name.must_equal 'bar'
69
+ end
70
+
71
+ it 'allows assigning unknown attributes' do
72
+ product = Product.new
73
+
74
+ product.wont_respond_to :foobar
75
+ product.must_respond_to :foobar=
76
+
77
+ product.foobar = 'bar'
78
+ product.foobar.must_equal 'bar'
79
+ end
80
+
81
+ describe '#respond_to' do
82
+ it 'returns true for known attributes' do
83
+ stub_get('products/1').to_return_json(data: [{ id: 1, name: 'RHEL' }])
84
+
85
+ product = Product.find(1)
86
+ product.must_respond_to :name
87
+ end
88
+
89
+ it 'returns false for unknown attributes that are not set' do
90
+ stub_get('products/1').to_return_json(data: [{ id: 1, name: 'RHEL' }])
91
+
92
+ product = Product.find(1)
93
+ product.wont_respond_to :title
94
+ end
95
+
96
+ it 'returns true for unknown attributes after it is set' do
97
+ product = Product.new
98
+ product.wont_respond_to :foobar
99
+
100
+ product.foobar = 'bar'
101
+ product.must_respond_to :foobar
102
+ Product.attributes.wont_include :foobar
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'nested hash attributes' do
108
+ it 'returns OpenStruct for nested hash' do
109
+ allowed_tags = %w(foo bar baz)
110
+ stub_get('nested-models/1').to_return_json(
111
+ data: [{
112
+ id: 1, name: 'RHEL',
113
+ nested: {
114
+ tag: 'foobar',
115
+ allowed_tags: allowed_tags.clone
116
+ }
117
+ }]
118
+ )
119
+
120
+ model = NestedModel.find(1)
121
+ model.must_respond_to :nested
122
+ model.nested.must_be_instance_of OpenStruct
123
+ model.nested.must_respond_to :tag
124
+ model.nested.must_respond_to :allowed_tags
125
+ model.nested.tag.must_equal 'foobar'
126
+ model.nested.allowed_tags.must_equal allowed_tags
127
+ end
128
+
129
+ it 'returns OpenStruct for deep nested hash' do
130
+ stub_get('nested-models/1').to_return_json(
131
+ data: [{
132
+ id: 1, name: 'RHEL',
133
+ nested: {
134
+ second_level: {
135
+ value: 'deep'
136
+ }
137
+ }
138
+ }]
139
+ )
140
+
141
+ model = NestedModel.find(1)
142
+ model.must_respond_to :nested
143
+
144
+ model.nested.must_respond_to :second_level
145
+ model.nested.second_level.must_be_instance_of OpenStruct
146
+ model.nested.second_level.must_respond_to :value
147
+ model.nested.second_level.value.must_equal 'deep'
148
+ end
149
+
150
+ it 'returns OpenStruct for even unregistered attributes' do
151
+ stub_get('nested-models/1').to_return_json(
152
+ data: [{
153
+ id: 1, name: 'RHEL',
154
+ unregistered_nested: {
155
+ value: 'unregistered'
156
+ }
157
+ }]
158
+ )
159
+
160
+ model = NestedModel.find(1)
161
+ model.must_respond_to :unregistered_nested
162
+ model.unregistered_nested.must_be_instance_of OpenStruct
163
+ model.unregistered_nested.must_respond_to :value
164
+ model.unregistered_nested.value.must_equal 'unregistered'
165
+ end
166
+ end
167
+
168
+
169
+ describe 'Custom ValueParser' do
170
+ subject { CustomParserModel }
171
+ it 'must return fixnum for age' do
172
+ stub_get('custom-parser-models/1').to_return_json(
173
+ data: [{
174
+ id: 1, name: 'RHEL', age: '20'
175
+ }]
176
+ )
177
+
178
+ model = subject.find(1)
179
+ model.must_respond_to :age
180
+ model.age.must_be_instance_of Fixnum
181
+ model.age.must_equal 20
182
+ end
183
+ end
184
+
185
+ # class AttributesTest < MiniTest::Test
186
+
187
+ # def test_equality
188
+ # assert_equal Product.new(id: 2, title: 'Fish'), Product.new(id: 2, title: 'Fish')
189
+ # refute_equal Product.new(id: 2, title: 'Fish'), Product.new(id: 1, title: 'Fish')
190
+ # refute_equal Product.new(id: 2, title: 'Fish'), 'not_a_spyke_object'
191
+ # refute_equal Product.new(id: 2, title: 'Fish'), Image.new(id: 2, title: 'Fish')
192
+ # refute_equal Product.new, Product.new
193
+ # refute_equal StepImage.new(id: 1), Image.new(id: 1)
194
+ # end
195
+
196
+ # def test_uniqueness
197
+ # product_1 = Product.new(id: 1)
198
+ # product_2 = Product.new(id: 1)
199
+ # product_3 = Product.new(id: 2)
200
+ # image_1 = Image.new(id: 2)
201
+ # records = [product_1, product_2, product_3, image_1]
202
+ # assert_equal [product_1, product_3, image_1], records.uniq
203
+ # end
204
+
205
+ # def test_super_with_explicit_attributes
206
+ # assert_equal nil, Product.new.description
207
+ # end
208
+
209
+ # def test_inheriting_explicit_attributes
210
+ # assert_equal nil, Image.new.description
211
+ # assert_equal nil, Image.new.caption
212
+ # assert_raises NoMethodError do
213
+ # Image.new.note
214
+ # end
215
+ # assert_equal nil, StepImage.new.description
216
+ # assert_equal nil, StepImage.new.caption
217
+ # assert_equal nil, StepImage.new.note
218
+ # end
219
+
220
+ # def test_inspect
221
+ # product = Product.new(id: 2, title: 'Pizza', description: 'Delicious')
222
+ # assert_equal '#<Product(products/(:id)) id: 2 title: "Pizza" description: "Delicious">', product.inspect
223
+ # product = Product.new
224
+ # assert_equal '#<Product(products/(:id)) id: nil >', product.inspect
225
+ # require 'pry'; binding.pry
226
+ # user = Product.new.build_user
227
+ # assert_equal '#<User(users/:uuid) id: nil >', user.inspect
228
+ # group = Product.new.groups.build
229
+ # assert_equal '#<Group(products/:product_id/groups/(:id)) id: nil product_id: nil>', group.inspect
230
+ # end
231
+ # end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require 'timecop' # for Timecop.freeze
3
+
4
+ # How it works
5
+ #
6
+ # The test records http response using +vcr+ as fixtures which are
7
+ # stored under spec/fixtures/vcr directory. After the response is
8
+ # stored as fixtures, vcr will start replaying those requests when
9
+ # same url is accessed by faraday.
10
+ #
11
+ # To deal with the cache expiry, the cache_store is set to in-memory so
12
+ # that the first fetch will be missed and handled by vcr. Time is
13
+ # frozen to 2016 which makes the cache valid/fresh after it is
14
+ # fetched/played once by vcr.
15
+
16
+ # NOTE: disable webmock to record a new test
17
+ # WebMock.disable!
18
+
19
+ # returns http cache status for all http calls made
20
+ # using faraday in the +&block+ passed
21
+ def record_cache_access(&block)
22
+ cache_access = []
23
+ record_status = lambda do |*args|
24
+ event = ActiveSupport::Notifications::Event.new(*args)
25
+ cache_access << event.payload[:cache_status]
26
+ end
27
+ ActiveSupport::Notifications.subscribed(record_status, 'http_cache.faraday', &block)
28
+ cache_access
29
+ end
30
+
31
+
32
+ describe 'Caching' do
33
+ before do
34
+ @old_config = PDC.config
35
+ config = @old_config.clone
36
+ config.disable_caching = false
37
+ config.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
38
+
39
+ PDC.config = config
40
+
41
+ VCR.insert_cassette fixture_name
42
+ Timecop.travel(Time.local(2016)) # based on fixtures
43
+ end
44
+
45
+ after do
46
+ Timecop.return
47
+ VCR.eject_cassette
48
+ PDC.config = @old_config
49
+ end
50
+
51
+ it 'caches response without query' do
52
+ cache = record_cache_access { PDC::V1::Release.scoped.contents! }
53
+ cache.must_equal [:miss]
54
+
55
+ cache = record_cache_access { PDC::V1::Release.scoped.contents! }
56
+ cache.must_equal [:fresh]
57
+
58
+ cache = record_cache_access { PDC::V1::Release.scoped.contents! }
59
+ cache.must_equal [:fresh]
60
+ end
61
+
62
+ it 'caches response with a query' do
63
+ cache = record_cache_access { PDC::V1::Release.page(2).contents! }
64
+ cache.must_equal [:miss]
65
+
66
+ cache = record_cache_access { PDC::V1::Release.page(2).contents! }
67
+ cache.must_equal [:fresh]
68
+ end
69
+
70
+ it 'caches response with multiple query' do
71
+ cache = record_cache_access do
72
+ PDC::V1::Release.page(2).page_size(30).contents!
73
+ PDC::V1::Release.page(2).page_size(30).contents!
74
+ end
75
+ cache.must_equal [:miss, :fresh]
76
+ end
77
+
78
+ it 'caches multiple response' do
79
+ cache = record_cache_access { PDC::V1::Release.where(active: true).all }
80
+
81
+ # must paginate so at least 2 pages
82
+ cache.length.must_be :>=, 2
83
+ cache.must_equal [:miss] * cache.length
84
+
85
+ second_time = record_cache_access { PDC::V1::Release.where(active: true).all }
86
+ second_time.must_equal [:fresh] * cache.length
87
+ end
88
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ #WebMock.disable! # enable to re-record
4
+
5
+ describe PDC do
6
+ before do
7
+ VCR.insert_cassette fixture_name
8
+ end
9
+
10
+ after do
11
+ VCR.eject_cassette
12
+ end
13
+
14
+ describe 'count' do
15
+ let(:release) { PDC::V1::Release }
16
+ let(:arch) { PDC::V1::Arch }
17
+
18
+ it 'must return number of resources' do
19
+ count = release.count
20
+ count.must_equal 54
21
+ end
22
+
23
+ it 'works with where' do
24
+ count = release.where(active: false).count
25
+ count.must_equal 0
26
+ end
27
+
28
+ it 'returns the total count and not items in page' do
29
+ total_count = arch.count
30
+ page_count = arch.page(2).count
31
+ page_count.must_equal total_count
32
+
33
+ arch_in_page = arch.page(2).contents!
34
+ arch_in_page.length.must_be :<, page_count
35
+ end
36
+
37
+ it '.page.count returns total count' do
38
+ pg2 = arch.page(2)
39
+ pg2_count = pg2.all.length
40
+
41
+ arches_count = pg2.count # must return all arches count
42
+ arches_count.wont_equal pg2_count
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'ap'
3
+
4
+ describe ModelWithIdentity do
5
+ subject { ModelWithIdentity }
6
+
7
+ describe '##primary_key' do
8
+ it 'must exist' do
9
+ class_methods = subject.methods - subject.instance_methods
10
+ class_methods.must_include :primary_key
11
+ end
12
+
13
+ it 'must default to class_name_id' do
14
+ subject.primary_key.must_equal 'model_with_identity_id'
15
+ end
16
+ end
17
+
18
+ describe 'uri' do
19
+ it 'must have a default uri' do
20
+ subject.uri.wont_be_nil
21
+ subject.uri.must_equal 'fixtures/model-with-identities/(:model_with_identity_id)'
22
+ end
23
+
24
+ it 'returns path with variables expanded for an object' do
25
+ instance = subject.new(subject.primary_key => :foobar)
26
+ expected_uri = PDC::Resource::Path.new('fixtures/model-with-identities/foobar').expanded
27
+ instance.uri.must_equal expected_uri
28
+ end
29
+ end
30
+
31
+ describe 'new' do
32
+ it 'returns nil for id' do
33
+ object = subject.new
34
+ object.id.must_be_nil
35
+ end
36
+
37
+ it 'can accept primary_key' do
38
+ pkey = subject.primary_key
39
+ instance = subject.new(pkey => 'something')
40
+ instance.id.must_equal 'something'
41
+ instance.model_with_identity_id.must_equal 'something'
42
+ end
43
+ end
44
+ end
45
+
46
+ describe CustomPrimaryKeyModel do
47
+ subject { CustomPrimaryKeyModel }
48
+
49
+ describe '##primary_key' do
50
+ it 'can be set' do
51
+ subject.primary_key.must_equal :foobar
52
+ end
53
+ end
54
+
55
+ it 'can accept primary_key' do
56
+ pkey = subject.primary_key
57
+ object = subject.new(pkey => 'something')
58
+ object.id.must_equal 'something'
59
+ object.foobar.must_equal 'something'
60
+ end
61
+
62
+ describe 'uri' do
63
+ it 'must use custom primary_key' do
64
+ subject.uri.must_equal 'fixtures/custom-primary-key-models/(:foobar)'
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ describe V1::Foobar do
71
+ subject { V1::Foobar }
72
+ it 'must have a pkey' do
73
+ subject.primary_key.must_equal 'foobar_id'
74
+ end
75
+
76
+ it 'must have a uri' do
77
+ subject.uri.must_equal 'fixtures/v1/foobars/(:foobar_id)'
78
+ end
79
+ end
80
+
81
+ describe 'attributes' do
82
+ it 'base must have primary key' do
83
+ ModelBase.new.id.must_be_nil
84
+ base = ModelBase.new(model_base_id: 1)
85
+ base.id.must_equal 1
86
+ base.attributes.must_include :model_base_id
87
+ end
88
+
89
+ it 'sub must not have primary key of base' do
90
+ Model.new.id.must_be_nil
91
+ model = Model.new(model_id: 3)
92
+ model.id.must_equal 3
93
+ model.attributes.must_include :model_id
94
+ model.attributes.wont_include :model_base_id
95
+ end
96
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe PDC do
4
+ before do
5
+ VCR.insert_cassette fixture_name
6
+ end
7
+
8
+ after do
9
+ VCR.eject_cassette
10
+ end
11
+
12
+ describe 'pagination' do
13
+ let(:arch) { PDC::V1::Arch }
14
+ let(:release_variant) { PDC::V1::ReleaseVariant }
15
+
16
+ it 'can iterate using each' do
17
+ count = arch.count # makes single call
18
+
19
+ all = arch.scoped.each_with_object([]) do |a, o|
20
+ o << a
21
+ end
22
+ count.must_equal all.length
23
+ end
24
+
25
+ it 'preserves the filters' do
26
+ variants = release_variant.where(type: 'variant', name: 'rh-common')
27
+ count = variants.count
28
+
29
+ all = variants.each_with_object([]) do |a, o|
30
+ o << a
31
+ end
32
+ count.must_equal all.length
33
+ end
34
+ end
35
+
36
+ describe 'page' do
37
+ subject { PDC::V1::Arch }
38
+ it 'returns resources on that page' do
39
+ pg2 = subject.page(2)
40
+ resources = pg2.contents!
41
+ resources.length.must_equal 20
42
+ end
43
+
44
+ it 'should not be in the list of attributes' do
45
+ pg2 = subject.page(2)
46
+ arch = pg2.all.first
47
+ arch.attributes.wont_include :page
48
+ arch.attributes.wont_include 'page'
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe PDC::Resource::Path do
4
+ subject { PDC::Resource::Path }
5
+
6
+ it 'can handle optional variables' do
7
+ assert_equal '/recipes/(:id)', subject.new('/recipes/(:id)').to_s
8
+ assert_equal '/recipes', subject.new('/recipes/(:id)').expanded
9
+ assert_equal '/recipes/2', subject.new('/recipes/(:id)', id: 2).expanded
10
+ end
11
+
12
+ it 'nested collections' do
13
+ path = subject.new('/users/:user_id/recipes/(:id)',
14
+ user_id: 1, status: 'published')
15
+
16
+ assert_equal [:user_id, :id], path.variables
17
+ assert_equal '/users/1/recipes', path.expanded
18
+ end
19
+
20
+
21
+ it 'nested_resource_path' do
22
+ assert_equal '/users/1/recipes/2', subject.new('/users/:user_id/recipes/:id', user_id: 1, id: 2).expanded
23
+ end
24
+
25
+ it 'raises if required_params are missing' do
26
+ lambda {
27
+ subject.new('/users/:user_id/recipes/(:id)', id: 2).expanded
28
+ }.must_raise PDC::InvalidPathError, 'Missing required params: user_id in /users/:user_id/recipes/(:id)'
29
+ end
30
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ class SearchableModel < Base
4
+ attributes :name, :product
5
+
6
+ ### NOTE: where() will have no impact since the data is hardcoded
7
+ def self.fake_data
8
+ [
9
+ { name: 'foobar-1', product: 'prod' },
10
+ { name: 'foobar-2', product: 'prod' },
11
+ { name: 'foobar-3', product: 'prod' },
12
+ ]
13
+ end
14
+
15
+ def self.fetch(params)
16
+ response = OpenStruct.new(
17
+ body: { data: fake_data },
18
+ status: 200,
19
+ env: OpenStruct.new(url: 'http://foobar')
20
+ )
21
+
22
+ PDC::Http::Result.new(response)
23
+ end
24
+ end
25
+
26
+ describe SearchableModel do
27
+ subject { SearchableModel }
28
+
29
+
30
+ describe '#where' do
31
+ it 'returns a scope with params' do
32
+ filters = { name: :foobar, age: 10 }
33
+ rel = subject.where(filters)
34
+
35
+ rel.must_be_instance_of PDC::Resource::Relation
36
+ rel.params.must_equal(filters)
37
+ end
38
+
39
+ it 'can be called multiple times' do
40
+ filter_name = { name: :foobar }
41
+ filter_age = { age: 10 }
42
+
43
+ rel = subject.where(filter_name).where(filter_age)
44
+ .where(filter_name)
45
+ rel.must_be_instance_of PDC::Resource::Relation
46
+
47
+ filters = filter_name.merge(filter_age)
48
+ rel.params.must_equal filters
49
+ end
50
+ end
51
+
52
+ describe '#find' do
53
+ let(:one_result) { [{ name: 'foobar2', product: 'prod' }] }
54
+ let(:no_result) { [] }
55
+
56
+ it 'returns a record' do
57
+ subject.stub(:fake_data, one_result) do
58
+ result = subject.find(:foobar)
59
+ result.must_be_instance_of SearchableModel
60
+ end
61
+ end
62
+
63
+ it 'raises MultipleResultsError more than a record exists' do
64
+ -> { subject.find(:foobar) }.must_raise PDC::MultipleResultsError
65
+ end
66
+
67
+ it 'raise ResourceNotFound when no record exists' do
68
+ subject.stub(:fake_data, no_result) do
69
+ -> { subject.find(:foobar) }.must_raise PDC::ResourceNotFound
70
+ end
71
+ end
72
+ end
73
+
74
+ it 'can call first' do
75
+ first = subject.first
76
+ first.must_be_instance_of subject
77
+ first.name.must_equal subject.fake_data.first[:name]
78
+ end
79
+
80
+ describe 'relation' do
81
+ it 'can call all on a relation' do
82
+ relation = subject.where(name: :foobar)
83
+ relation.must_be_instance_of PDC::Resource::Relation
84
+ results = relation.all
85
+ results.length.must_equal subject.fake_data.length
86
+ results.first.must_be_instance_of subject
87
+ end
88
+
89
+ it 'all works with resource' do
90
+ results = subject.all
91
+ results.length.must_equal subject.fake_data.length
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe :find do
4
+ it 'finds a specific resource' do
5
+ stub_get('products/1')
6
+ .to_return_json(data: [{ product_id: 1, title: 'foobar' }])
7
+
8
+ product = Fixtures::Product.find(1)
9
+ product.id.must_equal 1
10
+ product.product_id.must_equal 1
11
+ product.title.must_equal 'foobar'
12
+ end
13
+
14
+ it 'must work when primary_key is user defined' do
15
+ stub_get('product-variants/v2')
16
+ .to_return_json(data: [{ v_id: 'v2', title: 'variant' }])
17
+
18
+ variant = Fixtures::ProductVariant.find('v2')
19
+ variant.id.must_equal 'v2'
20
+ variant.v_id.must_equal 'v2'
21
+ variant.title.must_equal 'variant'
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe PDC::Resource::ValueParser do
4
+ subject { PDC::Resource::ValueParser }
5
+
6
+ it 'must respond_to? parse' do
7
+ subject.must_respond_to :parse
8
+ end
9
+
10
+ describe '#parse' do
11
+ it 'must return value for plain types' do
12
+ subject.parse(:foobar).must_equal :foobar
13
+ end
14
+ end
15
+ end
File without changes