wcc-api 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 "includes link to current page" do
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 "includes link to next page when there is a next page" do
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 "does not include link to next page when this is the last page" do
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 "includes link to previous page when there is a previous page" do
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 "does not include link to next page when this is the last page" do
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
-
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WCC
2
4
  module API
3
- VERSION = "0.2.0"
5
+ VERSION = '0.3.0'
4
6
  end
5
7
  end
@@ -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: -> (params) { url_for(params) }
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
+ }
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixturesHelper
4
+ def load_fixture(file_name)
5
+ file = "#{File.dirname(__FILE__)}/../fixtures/#{file_name}"
6
+ return File.read(file) if File.exist?(file)
7
+ end
8
+ 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