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.
@@ -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