wcc-api 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +41 -0
- data/.env.example +0 -0
- data/.rspec +2 -0
- data/.rubocop.yml +190 -0
- data/.rubocop_todo.yml +63 -0
- data/Gemfile +2 -0
- data/Rakefile +2 -1
- data/bin/rspec +18 -0
- data/lib/wcc/api.rb +6 -8
- data/lib/wcc/api/base_query.rb +10 -10
- data/lib/wcc/api/controller_helpers.rb +5 -3
- data/lib/wcc/api/json.rb +2 -0
- data/lib/wcc/api/json/pagination.rb +16 -13
- data/lib/wcc/api/railtie.rb +3 -1
- data/lib/wcc/api/rest_client.rb +83 -0
- data/lib/wcc/api/rest_client/api_error.rb +26 -0
- data/lib/wcc/api/rest_client/http_adapter.rb +19 -0
- data/lib/wcc/api/rest_client/response.rb +135 -0
- data/lib/wcc/api/rest_client/typhoeus_adapter.rb +34 -0
- data/lib/wcc/api/rspec.rb +2 -1
- data/lib/wcc/api/rspec/cache_header_examples.rb +15 -13
- data/lib/wcc/api/rspec/collection_matchers.rb +4 -2
- data/lib/wcc/api/rspec/pagination_examples.rb +8 -8
- data/lib/wcc/api/version.rb +3 -1
- data/lib/wcc/api/view_helpers.rb +3 -3
- data/spec/fixtures/contentful/entries.json +80 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/fixtures_helper.rb +8 -0
- data/spec/wcc/api/rest_client_spec.rb +266 -0
- data/wcc-api.gemspec +23 -13
- metadata +150 -10
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
RSpec.shared_examples_for :linked_pagination_object do |url_method, base_options, total|
|
2
|
-
it
|
4
|
+
it 'includes link to current page' do
|
3
5
|
url = public_send(
|
4
6
|
url_method,
|
5
7
|
base_options.merge(limit: 2, offset: 2)
|
@@ -9,20 +11,19 @@ RSpec.shared_examples_for :linked_pagination_object do |url_method, base_options
|
|
9
11
|
expect(subject['_links']['self']).to eq(url)
|
10
12
|
end
|
11
13
|
|
12
|
-
it
|
14
|
+
it 'includes link to next page when there is a next page' do
|
13
15
|
get public_send(url_method, limit: 2)
|
14
16
|
url = public_send(url_method,
|
15
|
-
base_options.merge(limit: 2, offset: 2)
|
16
|
-
)
|
17
|
+
base_options.merge(limit: 2, offset: 2))
|
17
18
|
expect(subject['_links']['next']).to eq(url)
|
18
19
|
end
|
19
20
|
|
20
|
-
it
|
21
|
+
it 'does not include link to next page when this is the last page' do
|
21
22
|
get public_send(url_method, limit: 2, offset: total - 1)
|
22
23
|
expect(subject['_links']['next']).to be_nil
|
23
24
|
end
|
24
25
|
|
25
|
-
it
|
26
|
+
it 'includes link to previous page when there is a previous page' do
|
26
27
|
get public_send(url_method, limit: 2, offset: 2)
|
27
28
|
url = public_send(
|
28
29
|
url_method,
|
@@ -31,9 +32,8 @@ RSpec.shared_examples_for :linked_pagination_object do |url_method, base_options
|
|
31
32
|
expect(subject['_links']['previous']).to eq(url)
|
32
33
|
end
|
33
34
|
|
34
|
-
it
|
35
|
+
it 'does not include link to next page when this is the last page' do
|
35
36
|
get public_send(url_method, limit: 2)
|
36
37
|
expect(subject['_links']['previous']).to be_nil
|
37
38
|
end
|
38
39
|
end
|
39
|
-
|
data/lib/wcc/api/version.rb
CHANGED
data/lib/wcc/api/view_helpers.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WCC::API
|
2
4
|
module ViewHelpers
|
3
|
-
|
4
5
|
def api_pagination_for(query:)
|
5
6
|
WCC::API::JSON::Pagination.new(
|
6
7
|
query,
|
7
|
-
url_for: ->
|
8
|
+
url_for: ->(params) { url_for(params) }
|
8
9
|
).to_builder
|
9
10
|
end
|
10
|
-
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
{
|
2
|
+
"sys": {
|
3
|
+
"type": "Array"
|
4
|
+
},
|
5
|
+
"total": 63,
|
6
|
+
"skip": 0,
|
7
|
+
"limit": 2,
|
8
|
+
"items": [
|
9
|
+
{
|
10
|
+
"sys": {
|
11
|
+
"space": {
|
12
|
+
"sys": {
|
13
|
+
"type": "Link",
|
14
|
+
"linkType": "Space",
|
15
|
+
"id": "hw5pse7y1ojx"
|
16
|
+
}
|
17
|
+
},
|
18
|
+
"id": "6xJzDTX2HCo0u4QKIuGCOu",
|
19
|
+
"type": "Entry",
|
20
|
+
"createdAt": "2018-11-02T19:09:46.884Z",
|
21
|
+
"updatedAt": "2018-11-02T19:09:46.884Z",
|
22
|
+
"environment": {
|
23
|
+
"sys": {
|
24
|
+
"id": "gburgett",
|
25
|
+
"type": "Link",
|
26
|
+
"linkType": "Environment"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"revision": 1,
|
30
|
+
"contentType": {
|
31
|
+
"sys": {
|
32
|
+
"type": "Link",
|
33
|
+
"linkType": "ContentType",
|
34
|
+
"id": "menuButton"
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"locale": "en-US"
|
38
|
+
},
|
39
|
+
"fields": {
|
40
|
+
"text": "What We Believe",
|
41
|
+
"externalLink": "http://www.watermark.org/fort-worth/about/beliefs"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"sys": {
|
46
|
+
"space": {
|
47
|
+
"sys": {
|
48
|
+
"type": "Link",
|
49
|
+
"linkType": "Space",
|
50
|
+
"id": "hw5pse7y1ojx"
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"id": "5yozzvgItUSYu4eI8yQ0ee",
|
54
|
+
"type": "Entry",
|
55
|
+
"createdAt": "2018-11-02T19:09:51.627Z",
|
56
|
+
"updatedAt": "2018-11-02T19:09:51.627Z",
|
57
|
+
"environment": {
|
58
|
+
"sys": {
|
59
|
+
"id": "gburgett",
|
60
|
+
"type": "Link",
|
61
|
+
"linkType": "Environment"
|
62
|
+
}
|
63
|
+
},
|
64
|
+
"revision": 1,
|
65
|
+
"contentType": {
|
66
|
+
"sys": {
|
67
|
+
"type": "Link",
|
68
|
+
"linkType": "ContentType",
|
69
|
+
"id": "section-block-text"
|
70
|
+
}
|
71
|
+
},
|
72
|
+
"locale": "en-US"
|
73
|
+
},
|
74
|
+
"fields": {
|
75
|
+
"internalTitle": "Frisco - Life Stage: Kids' Ministry",
|
76
|
+
"body": "## Kids' Ministry\n\nKids' Ministry is availible for infants and children through 5th grade at both 9 AM and 11 AM services. "
|
77
|
+
}
|
78
|
+
}
|
79
|
+
]
|
80
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
SPEC_DIR = File.dirname(__FILE__)
|
4
|
+
FIXTURES_DIR = File.join(SPEC_DIR, 'fixtures')
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.join(SPEC_DIR, '..', 'lib')
|
7
|
+
$LOAD_PATH.unshift SPEC_DIR
|
8
|
+
|
9
|
+
require 'dotenv'
|
10
|
+
Dotenv.load
|
11
|
+
|
12
|
+
require 'webmock/rspec'
|
13
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
14
|
+
|
15
|
+
require 'wcc/api'
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.expect_with :rspec do |expectations|
|
19
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
20
|
+
end
|
21
|
+
|
22
|
+
config.mock_with :rspec do |mocks|
|
23
|
+
mocks.verify_partial_doubles = true
|
24
|
+
end
|
25
|
+
|
26
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
27
|
+
|
28
|
+
config.include FixturesHelper
|
29
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'wcc/api/rest_client'
|
5
|
+
|
6
|
+
RSpec.describe WCC::API::RestClient do
|
7
|
+
describe 'initialize' do
|
8
|
+
after do
|
9
|
+
described_class::ADAPTERS = {
|
10
|
+
typhoeus: ['typhoeus', '~> 1.0'],
|
11
|
+
http: ['http', '> 1.0', '< 3.0']
|
12
|
+
}.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'fails to load when no adapter gem found' do
|
16
|
+
expect do
|
17
|
+
described_class::ADAPTERS = {
|
18
|
+
asdf: ['asdf', '~> 1.0']
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
described_class.new(
|
22
|
+
api_url: 'https://cdn.contentful.com'
|
23
|
+
)
|
24
|
+
end.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'fails to load when gem is wrong version' do
|
28
|
+
expect do
|
29
|
+
described_class::ADAPTERS = {
|
30
|
+
http: ['http', '< 1.0']
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
described_class.new(
|
34
|
+
api_url: 'https://cdn.contentful.com'
|
35
|
+
)
|
36
|
+
end.to raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'loads proc as adapter' do
|
40
|
+
described_class::ADAPTERS = {}.freeze
|
41
|
+
resp = double(body: 'test body', code: 200)
|
42
|
+
|
43
|
+
# act
|
44
|
+
client = described_class.new(
|
45
|
+
api_url: 'https://cdn.contentful.com',
|
46
|
+
adapter: proc { resp }
|
47
|
+
)
|
48
|
+
resp = client.get('http://asdf.com')
|
49
|
+
|
50
|
+
# assert
|
51
|
+
expect(resp.raw_body).to eq('test body')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'fails to load when adapter is not invokeable' do
|
55
|
+
described_class::ADAPTERS = {}.freeze
|
56
|
+
|
57
|
+
expect do
|
58
|
+
described_class::ADAPTERS = {
|
59
|
+
http: ['http', '< 1.0']
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
described_class.new(
|
63
|
+
api_url: 'https://cdn.contentful.com',
|
64
|
+
adapter: :whoopsie
|
65
|
+
)
|
66
|
+
end.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
described_class::ADAPTERS.keys.each do |adapter|
|
71
|
+
context "with #{adapter} adapter" do
|
72
|
+
subject(:client) do
|
73
|
+
described_class.new(
|
74
|
+
api_url: 'https://cdn.contentful.com/spaces/1234',
|
75
|
+
adapter: adapter
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:entries) do
|
80
|
+
::JSON.parse(load_fixture('contentful/entries.json'))
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'get' do
|
84
|
+
it 'gets entries with query params' do
|
85
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries?limit=2')
|
86
|
+
.to_return(body: load_fixture('contentful/entries.json'))
|
87
|
+
|
88
|
+
# act
|
89
|
+
resp = client.get('entries', limit: 2)
|
90
|
+
|
91
|
+
# assert
|
92
|
+
resp.assert_ok!
|
93
|
+
expect(resp.code).to eq(200)
|
94
|
+
expect(resp.to_json['items'].map { |i| i.dig('sys', 'id') }).to eq(
|
95
|
+
%w[6xJzDTX2HCo0u4QKIuGCOu 5yozzvgItUSYu4eI8yQ0ee]
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can query entries with query param' do
|
100
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
101
|
+
.with(query: {
|
102
|
+
'content_type' => 'menuButton',
|
103
|
+
'fields.text' => 'Ministries'
|
104
|
+
})
|
105
|
+
.to_return(body: load_fixture('contentful/entries.json'))
|
106
|
+
|
107
|
+
# act
|
108
|
+
resp = client.get('entries',
|
109
|
+
content_type: 'menuButton',
|
110
|
+
'fields.text' => 'Ministries')
|
111
|
+
|
112
|
+
# assert
|
113
|
+
resp.assert_ok!
|
114
|
+
expect(resp.code).to eq(200)
|
115
|
+
expect(resp.to_json['items'].map { |i| i.dig('sys', 'id') }).to eq(
|
116
|
+
%w[6xJzDTX2HCo0u4QKIuGCOu 5yozzvgItUSYu4eI8yQ0ee]
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'follows redirects' do
|
121
|
+
stub_request(:get, 'http://jtj.watermark.org/api')
|
122
|
+
.to_return(status: 301, headers: { 'Location' => 'https://jtj.watermark.org/api' })
|
123
|
+
stub_request(:get, 'https://jtj.watermark.org/api')
|
124
|
+
.to_return(body: '{ "links": { "entries": "/entries" } }')
|
125
|
+
|
126
|
+
client = described_class.new(
|
127
|
+
api_url: 'http://jtj.watermark.org/'
|
128
|
+
)
|
129
|
+
|
130
|
+
# act
|
131
|
+
resp = client.get('/api')
|
132
|
+
|
133
|
+
# assert
|
134
|
+
resp.assert_ok!
|
135
|
+
expect(resp.to_json['links']).to_not be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'paginates directly when block given' do
|
139
|
+
page1 = entries.merge('total' => 7)
|
140
|
+
page2 = entries.merge('total' => 7, 'skip' => 2)
|
141
|
+
page3 = entries.merge('total' => 7, 'skip' => 4)
|
142
|
+
page4 = entries.merge('total' => 7, 'skip' => 6)
|
143
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
144
|
+
.with(query: { 'limit' => 2 })
|
145
|
+
.to_return(body: page1.to_json)
|
146
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
147
|
+
.with(query: { 'limit' => 2, 'skip' => 2 })
|
148
|
+
.to_return(body: page2.to_json)
|
149
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
150
|
+
.with(query: { 'limit' => 2, 'skip' => 4 })
|
151
|
+
.to_return(body: page3.to_json)
|
152
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
153
|
+
.with(query: { 'limit' => 2, 'skip' => 6 })
|
154
|
+
.to_return(body: page4.to_json)
|
155
|
+
|
156
|
+
# act
|
157
|
+
resp = client.get('entries', limit: 2)
|
158
|
+
|
159
|
+
# assert
|
160
|
+
resp.assert_ok!
|
161
|
+
num_pages = 0
|
162
|
+
resp.each_page do |page|
|
163
|
+
expect(page.to_json['items'].length).to be <= 2
|
164
|
+
num_pages += 1
|
165
|
+
end
|
166
|
+
expect(num_pages).to eq(4)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'does lazy pagination' do
|
170
|
+
page1 = entries.merge('total' => 7)
|
171
|
+
page2 = entries.merge('total' => 7, 'skip' => 2)
|
172
|
+
page3 = entries.merge('total' => 7, 'skip' => 4)
|
173
|
+
page4 = entries.merge('total' => 7, 'skip' => 6, 'items' => [entries['items'][0]])
|
174
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
175
|
+
.with(query: { 'limit' => 2 })
|
176
|
+
.to_return(body: page1.to_json)
|
177
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
178
|
+
.with(query: { 'limit' => 2, 'skip' => 2 })
|
179
|
+
.to_return(body: page2.to_json)
|
180
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
181
|
+
.with(query: { 'limit' => 2, 'skip' => 4 })
|
182
|
+
.to_return(body: page3.to_json)
|
183
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
184
|
+
.with(query: { 'limit' => 2, 'skip' => 6 })
|
185
|
+
.to_return(body: page4.to_json)
|
186
|
+
|
187
|
+
# act
|
188
|
+
resp = client.get('entries', limit: 2)
|
189
|
+
|
190
|
+
# assert
|
191
|
+
resp.assert_ok!
|
192
|
+
pages = resp.each_page
|
193
|
+
expect(pages).to be_a(Enumerator::Lazy)
|
194
|
+
pages =
|
195
|
+
pages.map do |page|
|
196
|
+
expect(page.to_json['items'].length).to be <= 2
|
197
|
+
page.to_json['items']
|
198
|
+
end
|
199
|
+
pages = pages.force
|
200
|
+
expect(pages.length).to eq(4)
|
201
|
+
expect(pages.flatten.map { |c| c.dig('sys', 'id') })
|
202
|
+
.to eq(%w[
|
203
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
204
|
+
5yozzvgItUSYu4eI8yQ0ee
|
205
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
206
|
+
5yozzvgItUSYu4eI8yQ0ee
|
207
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
208
|
+
5yozzvgItUSYu4eI8yQ0ee
|
209
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
210
|
+
])
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'does not paginate if only the first page is taken' do
|
214
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
215
|
+
.with(query: { 'limit' => 2 })
|
216
|
+
.to_return(body: load_fixture('contentful/entries.json'))
|
217
|
+
|
218
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
219
|
+
.with(query: { 'limit' => 2, 'skip' => 2 })
|
220
|
+
.to_raise(StandardError.new('Should not execute request for second page'))
|
221
|
+
|
222
|
+
# act
|
223
|
+
resp = client.get('entries', limit: 2)
|
224
|
+
|
225
|
+
# assert
|
226
|
+
resp.assert_ok!
|
227
|
+
items = resp.items.take(2)
|
228
|
+
expect(items.map { |c| c.dig('sys', 'id') }.force)
|
229
|
+
.to eq(%w[
|
230
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
231
|
+
5yozzvgItUSYu4eI8yQ0ee
|
232
|
+
])
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'memoizes pages' do
|
236
|
+
page1 = entries.merge('total' => 4)
|
237
|
+
page2 = entries.merge('total' => 4, 'skip' => 2)
|
238
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
239
|
+
.with(query: { 'limit' => 2 })
|
240
|
+
.to_return(body: page1.to_json)
|
241
|
+
.times(1)
|
242
|
+
stub_request(:get, 'https://cdn.contentful.com/spaces/1234/entries')
|
243
|
+
.with(query: { 'limit' => 2, 'skip' => 2 })
|
244
|
+
.to_return(body: page2.to_json)
|
245
|
+
.times(1)
|
246
|
+
|
247
|
+
# act
|
248
|
+
resp = client.get('entries', limit: 2)
|
249
|
+
|
250
|
+
# assert
|
251
|
+
resp.assert_ok!
|
252
|
+
# first pagination
|
253
|
+
expect(resp.items.count).to eq(4)
|
254
|
+
# should be memoized
|
255
|
+
expect(resp.items.map { |c| c.dig('sys', 'id') }.force)
|
256
|
+
.to eq(%w[
|
257
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
258
|
+
5yozzvgItUSYu4eI8yQ0ee
|
259
|
+
6xJzDTX2HCo0u4QKIuGCOu
|
260
|
+
5yozzvgItUSYu4eI8yQ0ee
|
261
|
+
])
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|