wcc-api 0.1.0 → 0.5.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.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'http'
4
+ require 'http'
5
+
6
+ module WCC::API
7
+ class RestClient
8
+ class HttpAdapter
9
+ def call(url, query, headers = {}, proxy = {})
10
+ if proxy[:host]
11
+ HTTP[headers].via(proxy[:host], proxy[:port], proxy[:username], proxy[:password])
12
+ .get(url, params: query)
13
+ else
14
+ HTTP[headers].get(url, params: query)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'api_error'
5
+
6
+ module WCC::API
7
+ class RestClient
8
+ class AbstractResponse
9
+ extend ::Forwardable
10
+
11
+ attr_reader :raw_response
12
+ attr_reader :raw_body
13
+ attr_reader :client
14
+ attr_reader :request
15
+
16
+ def_delegators :raw_response, :status, :headers
17
+ alias_method :code, :status
18
+
19
+ def body
20
+ @body ||= ::JSON.parse(raw_body)
21
+ end
22
+ alias_method :to_json, :body
23
+
24
+ def initialize(client, request, raw_response)
25
+ @client = client
26
+ @request = request
27
+ @raw_response = raw_response
28
+ @raw_body = raw_response.body.to_s
29
+ end
30
+
31
+ def skip
32
+ throw new NotImplementedError, 'Please implement "skip" parsing in response class'
33
+ end
34
+
35
+ def count
36
+ throw new NotImplementedError, 'Please implement "count" parsing in response class'
37
+ end
38
+
39
+ def collection_response?
40
+ page_items.nil? ? false : true
41
+ end
42
+
43
+ def page_items
44
+ throw new NotImplementedError, 'Please implement "page_items" parsing in response class'
45
+ end
46
+
47
+ def error_message
48
+ parsed_message =
49
+ begin
50
+ body.dig('error', 'message') || body.dig('message')
51
+ rescue ::JSON::ParserError
52
+ nil
53
+ end
54
+ parsed_message || "#{code}: #{raw_response.body}"
55
+ end
56
+
57
+ def next_page?
58
+ return false unless collection_response?
59
+ return false if count.nil?
60
+
61
+ page_items.length + skip < count
62
+ end
63
+
64
+ def next_page
65
+ return unless next_page?
66
+
67
+ @next_page ||= @client.get(
68
+ @request[:url],
69
+ (@request[:query] || {}).merge(next_page_query)
70
+ )
71
+ @next_page.assert_ok!
72
+ end
73
+
74
+ def assert_ok!
75
+ return self if code >= 200 && code < 300
76
+
77
+ raise ApiError[code], self
78
+ end
79
+
80
+ # This method has a bit of complexity that is better kept in one location
81
+ def each_page(&block)
82
+ raise ArgumentError, 'Not a collection response' unless collection_response?
83
+
84
+ ret =
85
+ Enumerator.new do |y|
86
+ y << self
87
+
88
+ if next_page?
89
+ next_page.each_page.each do |page|
90
+ y << page
91
+ end
92
+ end
93
+ end
94
+
95
+ if block_given?
96
+ ret.map(&block)
97
+ else
98
+ ret.lazy
99
+ end
100
+ end
101
+
102
+ def items
103
+ return unless collection_response?
104
+
105
+ each_page.flat_map(&:page_items)
106
+ end
107
+
108
+ def first
109
+ raise ArgumentError, 'Not a collection response' unless collection_response?
110
+
111
+ page_items.first
112
+ end
113
+
114
+ def next_page_query
115
+ return unless collection_response?
116
+
117
+ {
118
+ skip: page_items.length + skip
119
+ }
120
+ end
121
+ end
122
+
123
+ class DefaultResponse < AbstractResponse
124
+ def skip
125
+ body['skip']
126
+ end
127
+
128
+ def count
129
+ body['total']
130
+ end
131
+
132
+ def page_items
133
+ body['items']
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ gem 'typhoeus'
5
+ require 'typhoeus'
6
+
7
+ class WCC::API::RestClient::TyphoeusAdapter
8
+ def get(url, params = {}, headers = {})
9
+ req = OpenStruct.new(params: params, headers: headers)
10
+ yield req if block_given?
11
+ Response.new(
12
+ Typhoeus.get(
13
+ url,
14
+ params: req.params,
15
+ headers: req.headers
16
+ )
17
+ )
18
+ end
19
+
20
+ def post(url, body, headers = {})
21
+ Response.new(
22
+ Typhoeus.post(
23
+ url,
24
+ body: body.is_a?(String) ? body : body.to_json,
25
+ headers: headers
26
+ )
27
+ )
28
+ end
29
+
30
+ def put(url, body, headers = {})
31
+ Response.new(
32
+ Typhoeus.put(
33
+ url,
34
+ body: body.is_a?(String) ? body : body.to_json,
35
+ headers: headers
36
+ )
37
+ )
38
+ end
39
+
40
+ def delete(url, query = {}, headers = {})
41
+ Response.new(
42
+ Typhoeus.delete(
43
+ url,
44
+ headers: headers
45
+ )
46
+ )
47
+ end
48
+
49
+ class Response < SimpleDelegator
50
+ def raw
51
+ __getobj__
52
+ end
53
+
54
+ def to_s
55
+ body&.to_s
56
+ end
57
+
58
+ def status
59
+ code
60
+ end
61
+ end
62
+ end
63
+
@@ -1,3 +1,5 @@
1
- require 'wcc/api/rspec/pagination_examples'
2
- require 'wcc/api/rspec/collection_matchers'
1
+ # frozen_string_literal: true
3
2
 
3
+ require 'wcc/api/rspec/cache_header_examples'
4
+ require 'wcc/api/rspec/collection_matchers'
5
+ require 'wcc/api/rspec/pagination_examples'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for 'cached resource defaults' do
4
+ it 'sets etag' do
5
+ expect(response.etag).to_not be_nil
6
+ end
7
+
8
+ it 'sets public: true' do
9
+ expect(response.cache_control[:public]).to be_truthy
10
+ end
11
+ end
12
+
13
+ RSpec.shared_examples_for 'cached member resource' do
14
+ include_examples 'cached resource defaults'
15
+
16
+ it 'sets last modified' do
17
+ expect(response.last_modified).to_not be_nil
18
+ end
19
+
20
+ it 'does not set max age' do
21
+ expect(response.cache_control[:max_age]).to be_nil
22
+ end
23
+
24
+ it 'does not set must_revalidate' do
25
+ expect(response.cache_control[:must_revalidate]).to be_nil
26
+ end
27
+ end
28
+
29
+ RSpec.shared_examples_for 'cached collection resource' do |max_age|
30
+ include_examples 'cached resource defaults'
31
+
32
+ it 'sets max age' do
33
+ expect(response.cache_control[:max_age]).to eq(max_age.to_s)
34
+ end
35
+
36
+ it 'sets must_revalidate: true' do
37
+ expect(response.cache_control[:must_revalidate]).to be_truthy
38
+ end
39
+
40
+ it 'does not set last modified' do
41
+ expect(response.last_modified).to be_nil
42
+ end
43
+ end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WCC::API::RSpec
2
4
  module CollectionMatchers
3
5
  def collection_match(coll1, coll2, matcher = :eq)
4
- coll1, coll2 = coll1.to_a, coll2.to_a
6
+ coll1 = coll1.to_a
7
+ coll2 = coll2.to_a
5
8
  expect(coll1.size).to eq(coll2.size)
6
9
 
7
10
  coll1.zip(coll2).each do |actual, expected|
@@ -10,4 +13,3 @@ module WCC::API::RSpec
10
13
  end
11
14
  end
12
15
  end
13
-
@@ -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.1.0"
5
+ VERSION = '0.5.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