the86-client 0.0.8 → 1.0.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.
- data/.gitignore +3 -0
- data/lib/the86-client.rb +1 -0
- data/lib/the86-client/connection.rb +14 -4
- data/lib/the86-client/conversation.rb +1 -0
- data/lib/the86-client/errors.rb +3 -0
- data/lib/the86-client/resource.rb +4 -4
- data/lib/the86-client/resource_collection.rb +37 -7
- data/lib/the86-client/response.rb +31 -0
- data/lib/the86-client/version.rb +1 -1
- data/spec/conversations_spec.rb +34 -8
- data/spec/response_spec.rb +28 -0
- data/spec/support/webmock.rb +3 -1
- metadata +5 -2
data/.gitignore
CHANGED
data/lib/the86-client.rb
CHANGED
@@ -24,6 +24,7 @@ module The86
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
# Insert a Faraday middleware at the top of the chain.
|
27
28
|
def prepend(*parameters)
|
28
29
|
@faraday.builder.insert(0, *parameters)
|
29
30
|
end
|
@@ -43,6 +44,11 @@ module The86
|
|
43
44
|
dispatch(:post, options)
|
44
45
|
end
|
45
46
|
|
47
|
+
private
|
48
|
+
|
49
|
+
# Dispatch the HTTP request.
|
50
|
+
# Returns the The86::Client::Response which contains the
|
51
|
+
# HTTP status code, headers and decoded response body.
|
46
52
|
def dispatch(method, options)
|
47
53
|
path = options.fetch(:path)
|
48
54
|
parameters = options[:parameters]
|
@@ -50,17 +56,21 @@ module The86
|
|
50
56
|
|
51
57
|
if parameters
|
52
58
|
path = Addressable::URI.parse(path).tap do |uri|
|
53
|
-
uri.query_values = parameters
|
59
|
+
uri.query_values = (uri.query_values || {}).merge(parameters)
|
54
60
|
end.to_s
|
55
61
|
end
|
56
62
|
|
57
63
|
headers = @faraday.headers.merge(options[:headers] || {})
|
58
64
|
response = @faraday.run_request(method, path, data, headers)
|
65
|
+
|
59
66
|
assert_http_status(response, options[:status])
|
60
|
-
response.body
|
61
|
-
end
|
62
67
|
|
63
|
-
|
68
|
+
::The86::Client::Response.new(
|
69
|
+
response.status,
|
70
|
+
response.headers,
|
71
|
+
response.body
|
72
|
+
)
|
73
|
+
end
|
64
74
|
|
65
75
|
def url
|
66
76
|
"%s://%s/api/v1" % [ Client.scheme, Client.domain ]
|
data/lib/the86-client/errors.rb
CHANGED
@@ -78,7 +78,7 @@ module The86
|
|
78
78
|
self.attributes = connection.get(
|
79
79
|
path: resource_path,
|
80
80
|
status: 200
|
81
|
-
)
|
81
|
+
).data
|
82
82
|
self
|
83
83
|
end
|
84
84
|
|
@@ -87,7 +87,7 @@ module The86
|
|
87
87
|
path: resource_path,
|
88
88
|
data: attributes,
|
89
89
|
status: 200
|
90
|
-
)
|
90
|
+
).data
|
91
91
|
end
|
92
92
|
|
93
93
|
def sendable_attributes
|
@@ -112,7 +112,7 @@ module The86
|
|
112
112
|
path: self.class.collection_path(@parent),
|
113
113
|
data: sendable_attributes,
|
114
114
|
status: 201
|
115
|
-
)
|
115
|
+
).data
|
116
116
|
end
|
117
117
|
|
118
118
|
def save_existing
|
@@ -120,7 +120,7 @@ module The86
|
|
120
120
|
path: resource_path,
|
121
121
|
data: sendable_attributes,
|
122
122
|
status: 200
|
123
|
-
)
|
123
|
+
).data
|
124
124
|
end
|
125
125
|
|
126
126
|
def connection
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "addressable/uri"
|
2
|
+
|
1
3
|
module The86::Client
|
2
4
|
class ResourceCollection
|
3
5
|
|
@@ -6,9 +8,7 @@ module The86::Client
|
|
6
8
|
# Connection is a The86::Client::Connection instance.
|
7
9
|
# Path is the API-relative path, e.g. "users".
|
8
10
|
# Klass is class of each record in the collection, e.g. User
|
9
|
-
#
|
10
|
-
# and not fetched in HTTP response, e.g. parent items.
|
11
|
-
# e.g. for conversations: { site: Site.new(slug: "...") }
|
11
|
+
# Parent is the parent resource of this collection and its items.
|
12
12
|
# Records is an array of hashes, for pre-populating the collection.
|
13
13
|
# e.g. when an API response contains collections of child resources.
|
14
14
|
def initialize(connection, path, klass, parent, records = nil)
|
@@ -30,7 +30,12 @@ module The86::Client
|
|
30
30
|
attr_writer :parameters
|
31
31
|
|
32
32
|
def with_parameters(parameters)
|
33
|
-
|
33
|
+
self.class.new(
|
34
|
+
@connection,
|
35
|
+
@path,
|
36
|
+
@klass,
|
37
|
+
@parent
|
38
|
+
).tap do |collection|
|
34
39
|
collection.parameters = parameters
|
35
40
|
end
|
36
41
|
end
|
@@ -54,10 +59,31 @@ module The86::Client
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
62
|
+
# Load the next page of records, based on the pagination header, e.g.
|
63
|
+
# Link: <http://example.org/api/v1/sites/a/conversations?bumped_before=time>; rel="next"
|
64
|
+
def more
|
65
|
+
if more?
|
66
|
+
self.class.new(
|
67
|
+
@connection,
|
68
|
+
Addressable::URI.parse(http_response.links[:next]).request_uri,
|
69
|
+
@klass,
|
70
|
+
@parent
|
71
|
+
)
|
72
|
+
else
|
73
|
+
raise PaginationError, %{Collection has no 'Link: <url>; rel="next"' header}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Whether there are more resources on a subsequent page.
|
78
|
+
# See documentation for #more method.
|
79
|
+
def more?
|
80
|
+
http_response.links.key? :next
|
81
|
+
end
|
82
|
+
|
57
83
|
# Cache array representation.
|
58
84
|
# Save building Resources for each record multiple times.
|
59
85
|
def to_a
|
60
|
-
@_to_a
|
86
|
+
@_to_a ||= super
|
61
87
|
end
|
62
88
|
|
63
89
|
def [](index)
|
@@ -66,13 +92,17 @@ module The86::Client
|
|
66
92
|
|
67
93
|
private
|
68
94
|
|
69
|
-
def
|
70
|
-
@
|
95
|
+
def http_response
|
96
|
+
@_http_response ||= @connection.get(
|
71
97
|
path: @path,
|
72
98
|
parameters: @parameters,
|
73
99
|
status: 200
|
74
100
|
)
|
75
101
|
end
|
76
102
|
|
103
|
+
def records
|
104
|
+
@records || http_response.data
|
105
|
+
end
|
106
|
+
|
77
107
|
end
|
78
108
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module The86::Client
|
2
|
+
|
3
|
+
# Representation of an HTTP response.
|
4
|
+
class Response
|
5
|
+
|
6
|
+
# status: The numeric HTTP status.
|
7
|
+
# headers: Hash of HTTP response headers.
|
8
|
+
# data: The decoded body of the response.
|
9
|
+
def initialize(status, headers, data)
|
10
|
+
@status = status
|
11
|
+
@headers = headers
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :status
|
16
|
+
attr_reader :headers
|
17
|
+
attr_reader :data
|
18
|
+
|
19
|
+
# See: http://tools.ietf.org/html/rfc5988
|
20
|
+
def links
|
21
|
+
@_links ||= {}.tap do |links|
|
22
|
+
Array(headers["Link"] || headers["link"]).map do |link|
|
23
|
+
link.match %r{\A<([^>]+)>;\s*rel="([^"]+)"\z}
|
24
|
+
end.compact.each do |match|
|
25
|
+
links[match[2].downcase.to_sym] = match[1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/the86-client/version.rb
CHANGED
data/spec/conversations_spec.rb
CHANGED
@@ -36,6 +36,28 @@ module The86::Client
|
|
36
36
|
c.id.must_equal 10
|
37
37
|
c.site.must_equal site
|
38
38
|
end
|
39
|
+
|
40
|
+
it "handles pagination headers" do
|
41
|
+
url = "#{conversations_url}?limit=2"
|
42
|
+
next_url = "#{url}&bumped_before=timestamp"
|
43
|
+
expect_get_conversations(
|
44
|
+
url: basic_auth_url(url),
|
45
|
+
response_body: [{id: 1}, {id: 2}],
|
46
|
+
response_headers: {"Link" => %{<#{next_url}>; rel="next"}}
|
47
|
+
)
|
48
|
+
expect_get_conversations(
|
49
|
+
url: basic_auth_url(next_url),
|
50
|
+
response_body: [{id: 3}, {id: 4}],
|
51
|
+
)
|
52
|
+
page1 = site.conversations.with_parameters(limit: 2)
|
53
|
+
page1.more?.must_equal true
|
54
|
+
|
55
|
+
page2 = page1.more
|
56
|
+
page2.more?.must_equal false
|
57
|
+
|
58
|
+
page1.map(&:id).must_equal([1,2])
|
59
|
+
page2.map(&:id).must_equal([3,4])
|
60
|
+
end
|
39
61
|
end
|
40
62
|
|
41
63
|
describe "creating conversations" do
|
@@ -64,7 +86,7 @@ module The86::Client
|
|
64
86
|
describe "finding a conversation" do
|
65
87
|
it "gets the conversation, loads data into the resource" do
|
66
88
|
expect_request(
|
67
|
-
url: "https://
|
89
|
+
url: basic_auth_url("https://example.org/api/v1/sites/test/conversations/4"),
|
68
90
|
method: :get,
|
69
91
|
status: 200,
|
70
92
|
response_body: {id: 4, posts: [{id: 8, content: "A post."}]},
|
@@ -77,8 +99,9 @@ module The86::Client
|
|
77
99
|
|
78
100
|
describe "hiding and unhiding a conversation" do
|
79
101
|
let(:conversation) { site.conversations.build(id: 2) }
|
80
|
-
let(:
|
81
|
-
let(:
|
102
|
+
let(:user_auth_url) { "#{conversations_url}/2" }
|
103
|
+
let(:site_auth_url) { user_auth_url.sub("//", "//user:pass@") }
|
104
|
+
let(:site_auth_url) { basic_auth_url(user_auth_url) }
|
82
105
|
let(:headers) { Hash.new }
|
83
106
|
def expectation(url, hidden_param)
|
84
107
|
{
|
@@ -92,20 +115,20 @@ module The86::Client
|
|
92
115
|
end
|
93
116
|
describe "without oauth" do
|
94
117
|
it "patches the conversation as hidden_by_site when no oauth_token" do
|
95
|
-
expect_request(expectation(
|
118
|
+
expect_request(expectation(site_auth_url, hidden_by_site: true))
|
96
119
|
conversation.hide
|
97
120
|
|
98
|
-
expect_request(expectation(
|
121
|
+
expect_request(expectation(site_auth_url, hidden_by_site: false))
|
99
122
|
conversation.unhide
|
100
123
|
end
|
101
124
|
end
|
102
125
|
describe "with oauth" do
|
103
126
|
let(:headers) { {"Authorization" => "Bearer secret"} }
|
104
127
|
it "patches the conversation as hidden_by_user when oauth_token present" do
|
105
|
-
expect_request(expectation(
|
128
|
+
expect_request(expectation(user_auth_url, hidden_by_user: true))
|
106
129
|
conversation.hide(oauth_token: "secret")
|
107
130
|
|
108
|
-
expect_request(expectation(
|
131
|
+
expect_request(expectation(user_auth_url, hidden_by_user: false))
|
109
132
|
conversation.unhide(oauth_token: "secret")
|
110
133
|
end
|
111
134
|
end
|
@@ -113,12 +136,15 @@ module The86::Client
|
|
113
136
|
|
114
137
|
def expect_get_conversations(options)
|
115
138
|
expect_request({
|
116
|
-
url: conversations_url
|
139
|
+
url: basic_auth_url(conversations_url),
|
117
140
|
method: :get,
|
118
141
|
status: 200,
|
119
142
|
}.merge(options))
|
120
143
|
end
|
121
144
|
|
145
|
+
def basic_auth_url(url)
|
146
|
+
url.sub("//", "//user:pass@")
|
147
|
+
end
|
122
148
|
end
|
123
149
|
|
124
150
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
module The86::Client
|
4
|
+
describe Response do
|
5
|
+
|
6
|
+
it "stores status, headers, data" do
|
7
|
+
r = Response.new(201, {"X-Test" => "test"}, {id: 4})
|
8
|
+
r.status.must_equal 201
|
9
|
+
r.headers.must_equal("X-Test" => "test")
|
10
|
+
r.data.must_equal(id: 4)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#links" do
|
14
|
+
let(:response) { Response.new(200, {"Link" => links}, nil) }
|
15
|
+
let(:links) { [
|
16
|
+
%{<https://example.org/page3>; rel="next"},
|
17
|
+
%{<http://example.org/page1?a=b>; rel="prev"},
|
18
|
+
] }
|
19
|
+
|
20
|
+
it "exposes RFC 5988 link headers" do
|
21
|
+
response.links[:next].must_equal "https://example.org/page3"
|
22
|
+
response.links[:prev].must_equal "http://example.org/page1?a=b"
|
23
|
+
response.links[:test].must_equal nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/spec/support/webmock.rb
CHANGED
@@ -26,8 +26,9 @@ module RequestExpectations
|
|
26
26
|
parameters = options[:parameters]
|
27
27
|
method = options.fetch(:method)
|
28
28
|
request_body = options[:request_body]
|
29
|
-
response_body = options[:response_body]
|
30
29
|
request_headers = options[:request_headers]
|
30
|
+
response_body = options[:response_body]
|
31
|
+
response_headers = options[:response_headers]
|
31
32
|
|
32
33
|
if parameters
|
33
34
|
url = Addressable::URI.parse(url).tap do |url|
|
@@ -41,6 +42,7 @@ module RequestExpectations
|
|
41
42
|
|
42
43
|
response = {status: options[:status] || 200}
|
43
44
|
response[:body] = JSON.generate(response_body) if response_body
|
45
|
+
response[:headers] = response_headers if response_headers
|
44
46
|
|
45
47
|
stub_and_assert_request(method, url).
|
46
48
|
with(request).
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: the86-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|
@@ -227,6 +227,7 @@ files:
|
|
227
227
|
- lib/the86-client/post.rb
|
228
228
|
- lib/the86-client/resource.rb
|
229
229
|
- lib/the86-client/resource_collection.rb
|
230
|
+
- lib/the86-client/response.rb
|
230
231
|
- lib/the86-client/site.rb
|
231
232
|
- lib/the86-client/user.rb
|
232
233
|
- lib/the86-client/version.rb
|
@@ -235,6 +236,7 @@ files:
|
|
235
236
|
- spec/post_spec.rb
|
236
237
|
- spec/posts_spec.rb
|
237
238
|
- spec/resource_spec.rb
|
239
|
+
- spec/response_spec.rb
|
238
240
|
- spec/spec_helper.rb
|
239
241
|
- spec/support/webmock.rb
|
240
242
|
- spec/user_spec.rb
|
@@ -270,6 +272,7 @@ test_files:
|
|
270
272
|
- spec/post_spec.rb
|
271
273
|
- spec/posts_spec.rb
|
272
274
|
- spec/resource_spec.rb
|
275
|
+
- spec/response_spec.rb
|
273
276
|
- spec/spec_helper.rb
|
274
277
|
- spec/support/webmock.rb
|
275
278
|
- spec/user_spec.rb
|