the86-client 0.0.1

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,66 @@
1
+ module The86::Client
2
+ class ResourceCollection
3
+
4
+ include Enumerable
5
+
6
+ # Connection is a The86::Client::Connection instance.
7
+ # Path is the API-relative path, e.g. "users".
8
+ # Klass is class of each record in the collection, e.g. User
9
+ # Attributes is a Hash of attributes common to all items in collection,
10
+ # and not fetched in HTTP response, e.g. parent items.
11
+ # e.g. for conversations: { site: Site.new(slug: "...") }
12
+ # Records is an array of hashes, for pre-populating the collection.
13
+ # e.g. when an API response contains collections of child resources.
14
+ def initialize(connection, path, klass, parent, records = nil)
15
+ @connection = connection
16
+ @path = path
17
+ @klass = klass
18
+ @parent = parent
19
+ @records = records
20
+ end
21
+
22
+ def build(attributes)
23
+ @klass.new(attributes.merge(parent: @parent))
24
+ end
25
+
26
+ def create(attributes)
27
+ build(attributes).tap(&:save)
28
+ end
29
+
30
+ # Find and load a resource.
31
+ #
32
+ # If Resource#url_id is overridden, specify the attribute name.
33
+ # TODO: the resource should know its URL attribute name.
34
+ #
35
+ # Note that this currently triggers an HTTP GET, then a POST:
36
+ # conversation.find(10).posts.create(attributes)
37
+ # As an alternative, this only triggers the HTTP POST:
38
+ # conversation.build(id: 10).posts.create(attributes)
39
+ def find(id, attribute = :id)
40
+ build(id: id).load
41
+ end
42
+
43
+ def each
44
+ records.each do |attributes|
45
+ yield build(attributes)
46
+ end
47
+ end
48
+
49
+ # Cache array representation.
50
+ # Save building Resources for each record multiple times.
51
+ def to_a
52
+ @_to_a = super
53
+ end
54
+
55
+ def [](index)
56
+ to_a[index]
57
+ end
58
+
59
+ private
60
+
61
+ def records
62
+ @records ||= @connection.get(path: @path, status: 200)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ module The86::Client
2
+ class Site < Resource
3
+
4
+ attribute :id, Integer
5
+ attribute :name, String
6
+ attribute :slug, String
7
+ attribute :created_at, DateTime
8
+ attribute :updated_at, DateTime
9
+
10
+ path "sites"
11
+ has_many :conversations, ->{ Conversation }
12
+
13
+ def url_id
14
+ slug
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module The86
2
+ module Client
3
+ class User < Resource
4
+
5
+ attribute :id, Integer
6
+ attribute :name, String
7
+ attribute :created_at, DateTime
8
+ attribute :updated_at, DateTime
9
+
10
+ path "users"
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module The86
2
+ module Client
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "spec_helper"
2
+
3
+ module The86::Client
4
+
5
+ describe "Conversations" do
6
+
7
+ describe "listing conversations" do
8
+ it "returns empty array for site without conversations" do
9
+ expect_get_conversations(response_body: [])
10
+ site.conversations.to_a.size.must_equal 0
11
+ end
12
+
13
+ it "returns collection of conversations" do
14
+ expect_get_conversations(response_body: [{id: 10}, {id: 12}])
15
+ conversations = site.conversations
16
+ conversations.to_a.size.must_equal 2
17
+ c = conversations.first
18
+ c.must_be_instance_of Conversation
19
+ c.id.must_equal 10
20
+ c.site.must_equal site
21
+ end
22
+ end
23
+
24
+ describe "creating conversations" do
25
+ it "posts and returns a conversation with the first post content" do
26
+ expect_request(
27
+ url: "https://example.org/api/v1/sites/test/conversations",
28
+ method: :post,
29
+ status: 201,
30
+ request_body: {content: "A new conversation."},
31
+ response_body: {id: 2, posts: [{id: 5, content: "A new conversation."}]},
32
+ request_headers: {"Authorization" => "Bearer secrettoken"},
33
+ )
34
+
35
+ c = site.conversations.create(
36
+ content: "A new conversation.",
37
+ oauth_token: "secrettoken",
38
+ )
39
+
40
+ c.id.must_equal 2
41
+ posts = c.posts
42
+ posts.to_a.length.must_equal 1
43
+ posts[0].content.must_equal "A new conversation."
44
+ end
45
+ end
46
+
47
+ describe "finding a conversation" do
48
+ it "gets the conversation, loads data into the resource" do
49
+ expect_request(
50
+ url: "https://user:pass@example.org/api/v1/sites/test/conversations/4",
51
+ method: :get,
52
+ status: 200,
53
+ response_body: {id: 4, posts: [{id: 8, content: "A post."}]},
54
+ )
55
+ c = site.conversations.find(4)
56
+ c.id.must_equal 4
57
+ c.posts.first.content.must_equal "A post."
58
+ end
59
+ end
60
+
61
+ def site
62
+ The86::Client.site("test")
63
+ end
64
+
65
+ def expect_get_conversations(options)
66
+ expect_request({
67
+ url: "https://user:pass@example.org/api/v1/sites/test/conversations",
68
+ method: :get,
69
+ status: 200,
70
+ }.merge(options))
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "spec_helper"
2
+
3
+ module The86::Client
4
+ describe OauthBearerAuthorization do
5
+
6
+ it "sets Authorization header, calls delegate" do
7
+ env = {request_headers: {}}
8
+
9
+ app = MiniTest::Mock.new
10
+ app.expect(:call, nil, [env])
11
+
12
+ OauthBearerAuthorization.new(app, "secret").call(env)
13
+
14
+ env[:request_headers]["Authorization"].must_equal "Bearer secret"
15
+ app.verify
16
+ end
17
+
18
+ end
19
+ end
data/spec/post_spec.rb ADDED
@@ -0,0 +1,15 @@
1
+ require_relative "spec_helper"
2
+ require "the86-client/post"
3
+
4
+ module The86::Client
5
+ describe Post do
6
+ describe "#reply?" do
7
+ it "is true when in_reply_to_id is present" do
8
+ Post.new(in_reply_to_id: 32).reply?.must_equal true
9
+ end
10
+ it "is false when in_reply_to_id is not present" do
11
+ Post.new(in_reply_to_id: nil).reply?.must_equal false
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,81 @@
1
+ require_relative "spec_helper"
2
+
3
+ module The86::Client
4
+
5
+ describe Post do
6
+
7
+ describe "replying to a post" do
8
+ it "sends in_reply_to_id" do
9
+ expect_request(
10
+ url: "https://example.org/api/v1/sites/test/conversations/32/posts",
11
+ method: :post,
12
+ status: 201,
13
+ request_body: {content: "Hi!", in_reply_to_id: 64},
14
+ response_body: {
15
+ id: 96, content: "Hi!", in_reply_to_id: 64, in_reply_to: {
16
+ id: 64,
17
+ content: "Hello!",
18
+ user: {
19
+ id: 128,
20
+ name: "Johnny Original"
21
+ }
22
+ }
23
+ },
24
+ request_headers: {"Authorization" => "Bearer SecretTokenHere"},
25
+ )
26
+ post = original_post.reply(
27
+ content: "Hi!",
28
+ oauth_token: "SecretTokenHere",
29
+ )
30
+ post.conversation.id.must_equal conversation.id
31
+ post.in_reply_to_id.must_equal original_post.id
32
+ post.in_reply_to.user.name.must_equal "Johnny Original"
33
+ post.content.must_equal "Hi!"
34
+ end
35
+ end
36
+
37
+ describe "following up to a conversation" do
38
+ it "creates new Post in the Conversation" do
39
+ expect_request(
40
+ url: "https://example.org/api/v1/sites/test/conversations/32/posts",
41
+ method: :post,
42
+ status: 201,
43
+ request_body: {content: "+1"},
44
+ response_body: {id: 96, content: "+1"},
45
+ request_headers: {"Authorization" => "Bearer SecretTokenHere"},
46
+ )
47
+ post = conversation.posts.create(
48
+ content: "+1",
49
+ oauth_token: "SecretTokenHere",
50
+ )
51
+ post.conversation.id.must_equal conversation.id
52
+ post.in_reply_to_id.must_equal nil
53
+ post.content.must_equal "+1"
54
+ end
55
+ end
56
+
57
+ describe "#user" do
58
+ let(:post) { Post.new(id: 1, user: {id: 2, name: "John Citizen"}) }
59
+ it "returns instance of The86::Client::User" do
60
+ post.user.must_be_instance_of(The86::Client::User)
61
+ end
62
+ it "contains the user details" do
63
+ post.user.name.must_equal "John Citizen"
64
+ end
65
+ end
66
+
67
+ def original_post
68
+ Post.new(id: 64, conversation: conversation, content: "Hello!")
69
+ end
70
+
71
+ def conversation
72
+ Conversation.new(id: 32, site: site)
73
+ end
74
+
75
+ def site
76
+ The86::Client.site("test")
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "spec_helper"
2
+
3
+ module The86::Client
4
+ describe Resource do
5
+
6
+ describe "equality" do
7
+ resource = Class.new(Resource) do
8
+ attribute :id, Integer
9
+ attribute :code, String
10
+ end
11
+
12
+ parent_resource = Class.new(Resource) do
13
+ attribute :id, Integer
14
+ end
15
+
16
+ it "is equal by attributes" do
17
+ resource.new(id: 1, code: "A").
18
+ must_equal resource.new(id: 1, code: "A")
19
+ end
20
+
21
+ it "is inequal by attributes" do
22
+ resource.new(id: 2, code: "A").
23
+ wont_equal resource.new(id: 1, code: "A")
24
+ end
25
+
26
+ it "is inequal by parent" do
27
+ resource.new(code: "A", parent: parent_resource.new(id: 1)).
28
+ wont_equal resource.new(code: "A", parent: parent_resource.new(id: 2))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ require "minitest/autorun"
2
+ require "turn"
3
+
4
+ require_relative "support/webmock"
5
+
6
+ $LOAD_PATH.unshift "./lib"
7
+ require "the86-client"
8
+
9
+ The86::Client.domain = "example.org"
10
+ The86::Client.credentials = ["user", "pass"]
@@ -0,0 +1,41 @@
1
+ require "json"
2
+ require "webmock/minitest"
3
+
4
+ WebMock.disable_net_connect!
5
+
6
+ ##
7
+ # WebMock request expectations.
8
+ module RequestExpectations
9
+
10
+ def self.included(klass)
11
+ klass.before { @expected_requests = [] }
12
+ klass.after { @expected_requests.each { |sr| assert_requested(sr) } }
13
+ end
14
+
15
+ def stub_and_assert_request(*parameters)
16
+ stub_request(*parameters).tap do |request|
17
+ @expected_requests << request
18
+ end
19
+ end
20
+
21
+ def expect_request(options)
22
+ url = options.fetch(:url)
23
+ method = options.fetch(:method)
24
+ request_body = options[:request_body]
25
+ response_body = options[:response_body]
26
+ request_headers = options[:request_headers]
27
+
28
+ request = {}
29
+ request[:body] = request_body if request_body
30
+ request[:headers] = request_headers if request_headers
31
+
32
+ response = {status: options[:status] || 200}
33
+ response[:body] = JSON.generate(response_body) if response_body
34
+
35
+ stub_and_assert_request(method, url).
36
+ with(request).
37
+ to_return(response)
38
+ end
39
+ end
40
+
41
+ MiniTest::Spec.send(:include, RequestExpectations)
@@ -0,0 +1,78 @@
1
+ require_relative "spec_helper"
2
+
3
+ module The86::Client
4
+
5
+ describe "Users" do
6
+
7
+ describe "creating a user" do
8
+ it "is successful with 200 OK" do
9
+ expect_create_user(response_body: {id: 1, name: "John Appleseed"})
10
+ user = The86::Client.users.create(name: "John Appleseed")
11
+ user.id.must_be_instance_of Fixnum
12
+ user.name.must_equal "John Appleseed"
13
+ end
14
+
15
+ it "raises error for 200 OK" do
16
+ expect_create_user(status: 200, response_body: {id: 1, name: "John Appleseed"})
17
+ ->{ The86::Client.users.create(name: "John Appleseed") }.must_raise Error
18
+ end
19
+
20
+ it "raises ValidationFailed for 422 response" do
21
+ expect_create_user(status: 422, request_body: {name: ""})
22
+ ->{ The86::Client.users.create(name: "") }.must_raise ValidationFailed
23
+ end
24
+
25
+ it "raises Unauthorized for 401 response" do
26
+ expect_create_user(status: 401, request_body: {name: "Test"})
27
+ ->{ The86::Client.users.create(name: "Test") }.must_raise Unauthorized
28
+ end
29
+ end
30
+
31
+ describe "updating a user (may not be supported by server)" do
32
+ it "PATCHes users/:id" do
33
+ time_before = DateTime.now - 86400
34
+ time_after = DateTime.now
35
+ expect_request(
36
+ url: "https://user:pass@example.org/api/v1/users/5",
37
+ method: :patch,
38
+ status: 200,
39
+ request_body: {name: "New Name"},
40
+ response_body: {id: 5, name: "New Name", updated_at: time_after}
41
+ )
42
+ user = User.new(
43
+ id: 5,
44
+ name: "Old Name",
45
+ updated_at: time_before
46
+ )
47
+ user.name = "New Name"
48
+ user.updated_at.to_s.must_equal time_before.to_s
49
+ user.save
50
+ user.updated_at.to_s.must_equal time_after.to_s
51
+ end
52
+ end
53
+
54
+ describe "listing users (may not be supported by server)" do
55
+ it "returns collection of users" do
56
+ expect_request(
57
+ url: "https://user:pass@example.org/api/v1/users",
58
+ method: :get,
59
+ status: 200,
60
+ response_body: [{id: 4, name: "Paul"}, {id: 8, name: "James"}]
61
+ )
62
+ users = The86::Client.users
63
+ users.to_a.length.must_equal 2
64
+ users.first.name.must_equal "Paul"
65
+ end
66
+ end
67
+
68
+ def expect_create_user(options = {})
69
+ expect_request({
70
+ url: "https://user:pass@example.org/api/v1/users",
71
+ method: :post,
72
+ status: 201,
73
+ }.merge(options))
74
+ end
75
+
76
+ end
77
+
78
+ end