the86-client 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|