the86-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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