social_net 0.2.19 → 0.2.20

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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -2
  3. data/lib/social_net/byte/api/configuration.rb +13 -0
  4. data/lib/social_net/byte/api/request.rb +71 -0
  5. data/lib/social_net/byte/config.rb +16 -0
  6. data/lib/social_net/byte/errors/response_error.rb +14 -0
  7. data/lib/social_net/byte/errors/unknown_post.rb +11 -0
  8. data/lib/social_net/byte/errors/unknown_user.rb +11 -0
  9. data/lib/social_net/byte/errors.rb +3 -0
  10. data/lib/social_net/byte/models/post.rb +66 -0
  11. data/lib/social_net/byte/models/user.rb +99 -0
  12. data/lib/social_net/byte/models.rb +2 -0
  13. data/lib/social_net/byte.rb +11 -0
  14. data/lib/social_net/version.rb +1 -1
  15. data/lib/social_net.rb +1 -0
  16. data/spec/social_net/byte/post_spec.rb +53 -0
  17. data/spec/social_net/byte/user_spec.rb +105 -0
  18. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_/given_an_existing_case-sensitive_username/returns_an_object_representing_that_user.yml +46 -0
  19. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_/given_an_unknown_username/.yml +45 -0
  20. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_id/given_an_existing_case-sensitive_ID/returns_an_object_representing_that_user.yml +46 -0
  21. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_id/given_an_unknown_ID/.yml +45 -0
  22. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_username/given_an_existing_case-insensitive_username/returns_an_object_representing_that_user.yml +46 -0
  23. data/spec/support/cassettes/SocialNet_Byte_Models_User/_find_by_username/given_an_unknown_username/.yml +45 -0
  24. data/spec/support/cassettes/SocialNet_Byte_Models_User/_posts/given_an_existing_user_with_no_posts/returns_an_empty_array_from_the_user.yml +129 -0
  25. data/spec/support/cassettes/SocialNet_Byte_Models_User/_posts/given_an_existing_user_with_paginated_posts/returns_an_array_of_video_posts_from_the_user.yml +175 -0
  26. data/spec/support/cassettes/SocialNet_Byte_Models_User/_posts/given_an_existing_user_with_posts/returns_an_array_of_video_posts_from_the_user.yml +132 -0
  27. metadata +35 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbbfd05e27a93524f64a2446337cac22a4208555a42fe72c38f1a663dd7cca5f
4
- data.tar.gz: cefc2822c9bb415800bee5012411fac8e4c99a7f395e47c9455a8c7dc40d366d
3
+ metadata.gz: e8f46909af7e663139816f04602accdab974ff58f69f2a12dbc0f070b43de3d2
4
+ data.tar.gz: a5045b140e87e1aedac869ce985ca80e6e694cdff52a9c9e4c6c513c0d0282e5
5
5
  SHA512:
6
- metadata.gz: 21f46409ba3b87d65c9327934ad75a1c7125727f8baf45f68fda0cdebf1f5c5fd8492c264e12677f1a66ea4723dabf055756853b7c822dc57cd343d7fcd5a62b
7
- data.tar.gz: a9b9753758d2096c3846c4bffb63ceb70dba9dfa308be9caf5d70984c20289485d51b49344496a3b51afe8f48ce526f2967943c3b27ac0bb80189dd0543a08f7
6
+ metadata.gz: 4e744faaf31d17afaca10e52e588609d63a9554c41fac6716bec42f9af0dd3a602aee7a94ba51c85774125f7ed138594aa49637afca5faa2fa606e52d1cbc956
7
+ data.tar.gz: 0ad5e30ada6dc472a0c91cdded05584d58c54b0e451656bc4c0ab3867cb8d78aef001f4b165177c131083c886aefffe5fb14187fbcf86bcceabbdf90cd7103e3
data/README.md CHANGED
@@ -1,9 +1,17 @@
1
1
  SocialNet - a Ruby client for social networks API
2
2
  ===========================================
3
3
 
4
- SocialNet helps you write apps that need to interact with Twitter, Instagram and Facebook.
4
+ SocialNet helps you write apps that need to interact with Instagram, Byte, Twitter, and Facebook.
5
5
 
6
- ## Note: Only Instagram works at the moment
6
+ ## Note: Only Instagram and Byte works at the moment
7
+
8
+ After [configuring your Byte app](#configuring-your-byte-app), you can run commands like:
9
+
10
+ ```ruby
11
+ user = SocialNet::Byte::User.find_by username: 'ollie'
12
+ user.username #=> "ollie"
13
+ user.follower_count #=> 300
14
+ ```
7
15
 
8
16
  After [configuring your Twitter app](#configuring-your-twitter-app), you can run commands like:
9
17
 
@@ -115,6 +123,65 @@ video.link #=> 'https://www.instagram.com/p/BW-nC7xg8ZX/'
115
123
  video.file #=> 'https://scontent.cdninstagram.com/t50.2886-16/20372137_156190564936990_2601958215176421376_n.mp4'
116
124
  ```
117
125
 
126
+ SocialNet::Byte::User
127
+ --------------------
128
+
129
+ Use [SocialNet::Byte::User]() to:
130
+
131
+ * retrieve a Byte user by username
132
+ * retrieve a Byte user by id
133
+ * retrieve posts of a Byte user
134
+
135
+ ```ruby
136
+ user = SocialNet::Byte::User.find_by username: 'ollie'
137
+ user.follower_count #=> 0
138
+
139
+ user = SocialNet::Byte::User.find_by id: 'PUEMKGYDBFAZ3HSRSAFGBAI5HA'
140
+ user.follower_count #=> 0
141
+
142
+ user.posts #=>
143
+ # {:posts=>
144
+ # [#<SocialNet::Byte::Models::Post:0x00007fb71b8705a8
145
+ # @author_id="PUEMKGYDBFAZ3HSRSAFGBAI5HA",
146
+ # @caption="i’m in ya house (ft. @Tishsimmonds)",
147
+ # @category="comedy",
148
+ # @comment_count=110,
149
+ # @date=1580391236,
150
+ # @id="WZPPQ5LQJZAAHP4BWFLM6PRBNM",
151
+ # @like_count=2439,
152
+ # @loop_count=34891,
153
+ # @thumb_src="https://e6k9t9a9.stackpathcdn.com/videos/5VVTQ4TTZBCRRHWDUPIZNUWDKM.jpg",
154
+ # @video_src="https://e6k9t9a9.stackpathcdn.com/videos/5VVTQ4TTZBCRRHWDUPIZNUWDKM-h264.mp4">],
155
+ # :next_page=>"CXWZB7CTMYLTA"}
156
+
157
+ user.posts(next_page: 'CXWZB7CTMYLTA') #=>
158
+ # {:posts=>
159
+ # [#<SocialNet::Byte::Models::Post:0x00007fb718468788
160
+ # @author_id="PUEMKGYDBFAZ3HSRSAFGBAI5HA",
161
+ # @caption="pov of a hungry person thinking byte is a delivery food app",
162
+ # @category="comedy",
163
+ # @comment_count=95,
164
+ # @date=1580043689,
165
+ # @id="PNHNHKO3RZF2HJVHSD64V4X3LE",
166
+ # @like_count=2605,
167
+ # @loop_count=23982,
168
+ # @share_url="https://byte.co/b/B69eUGbPcDM",
169
+ # @thumb_src="https://e6k9t9a9.stackpathcdn.com/videos/XRIRCMQZGBAUDLCWUJQPHGYYEE.jpg",
170
+ # @video_src="https://e6k9t9a9.stackpathcdn.com/videos/XRIRCMQZGBAUDLCWUJQPHGYYEE-h264.mp4">,
171
+ # :next_page=>"CXWPROPUQCCNQ"}
172
+ ```
173
+
174
+ Use [SocialNet::Byte::Post]() to:
175
+
176
+ * retrieve a Byte post by id
177
+
178
+ ```ruby
179
+ post = SocialNet::Byte::Post.find_by id: 'WZPPQ5LQJZAAHP4BWFLM6PRBNM'
180
+
181
+ post.caption #=> "i’m in ya house (ft. @Tishsimmonds)"
182
+ post.video_src #=> "https://e6k9t9a9.stackpathcdn.com/videos/5VVTQ4TTZBCRRHWDUPIZNUWDKM-h264.mp4"
183
+ ```
184
+
118
185
  SocialNet::Facebook::Page
119
186
  --------------------
120
187
 
@@ -223,6 +290,40 @@ end
223
290
  so use the approach that you prefer.
224
291
  If a variable is set in both places, then `SocialNet::Instagram.configure` takes precedence.
225
292
 
293
+ Configuring your Byte app
294
+ ============================
295
+
296
+ Once the app is created, copy your access token and add it to your
297
+ code with the following snippet of code (replacing with your own access token)
298
+ :
299
+
300
+ ```ruby
301
+ SocialNet::Byte.configure do |config|
302
+ config.access_token = 'abcdefg'
303
+ end
304
+ ```
305
+
306
+ Configuring with environment variables
307
+ --------------------------------------
308
+
309
+ As an alternative to the approach above, you can configure your app with
310
+ a variable. Setting the following environment variable:
311
+
312
+ ```bash
313
+ export BYTE_ACCESS_TOKEN='abcdefg'
314
+ ```
315
+
316
+ is equivalent to configuring your app with the initializer:
317
+
318
+ ```ruby
319
+ SocialNet::Byte.configure do |config|
320
+ config.access_token = 'abcdefg'
321
+ end
322
+ ```
323
+
324
+ so use the approach that you prefer.
325
+ If a variable is set in both places, then `SocialNet::Byte.configure` takes precedence.
326
+
226
327
  Configuring your Facebook app
227
328
  ============================
228
329
 
@@ -0,0 +1,13 @@
1
+ module SocialNet
2
+ module Byte
3
+ module Api
4
+ class Configuration
5
+ attr_accessor :access_token
6
+
7
+ def initialize
8
+ @access_token = ENV['BYTE_ACCESS_TOKEN']
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ require 'social_net/byte/errors/response_error'
2
+ require 'social_net/byte/errors/unknown_user'
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+
6
+ module SocialNet
7
+ module Byte
8
+ module Api
9
+ class Request
10
+ def initialize(attrs = {})
11
+ @host = 'api.byte.co'
12
+ @username = attrs[:username]
13
+ @endpoint = attrs.fetch :endpoint, "/account/id/#{@username}/posts"
14
+ @block = attrs.fetch :block, -> (request) {add_access_token_and_cursor! request}
15
+ @next_page = attrs[:next_page] if attrs[:next_page]
16
+ @method = attrs.fetch :method, :get
17
+ end
18
+
19
+ def run
20
+ print "#{as_curl}\n"
21
+ case response = run_http_request
22
+ when Net::HTTPOK
23
+ JSON response.body
24
+ else
25
+ raise Errors::ResponseError, response
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def run_http_request
32
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
33
+ http.request http_request
34
+ end
35
+ end
36
+
37
+ def http_request
38
+ http_class = "Net::HTTP::#{@method.capitalize}".constantize
39
+ @http_request ||= http_class.new(uri.request_uri).tap do |request|
40
+ @block.call request
41
+ end
42
+ end
43
+
44
+ def uri
45
+ @uri ||= URI::HTTPS.build host: @host, path: @endpoint, query: query
46
+ end
47
+
48
+ def add_access_token_and_cursor!(request)
49
+ request.add_field 'Authorization', SocialNet::Byte.configuration.access_token
50
+ end
51
+
52
+ def query
53
+ {}.tap do |query|
54
+ query.merge! cursor: @next_page if @next_page
55
+ end.to_param
56
+ end
57
+
58
+ def as_curl
59
+ 'curl'.tap do |curl|
60
+ curl << " -X #{http_request.method}"
61
+ http_request.each_header do |name, value|
62
+ curl << %Q{ -H "#{name}: #{value}"}
63
+ end
64
+ curl << %Q{ -d '#{http_request.body}'} if http_request.body
65
+ curl << %Q{ "#{@uri.to_s}"}
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ require 'social_net/byte/api/configuration'
2
+
3
+ module SocialNet
4
+ module Byte
5
+ module Config
6
+ def configure
7
+ yield configuration if block_given?
8
+ end
9
+
10
+ def configuration
11
+ @configuration ||= Api::Configuration.new
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,14 @@
1
+ module SocialNet
2
+ module Byte
3
+ module Errors
4
+ class ResponseError < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(response = {})
8
+ @response = response
9
+ super response
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module SocialNet
2
+ module Byte
3
+ module Errors
4
+ class UnknownPost < StandardError
5
+ def message
6
+ 'Unknown post'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module SocialNet
2
+ module Byte
3
+ module Errors
4
+ class UnknownUser < StandardError
5
+ def message
6
+ 'Unknown user'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'social_net/byte/errors/response_error'
2
+ require 'social_net/byte/errors/unknown_user'
3
+ require 'social_net/byte/errors/unknown_post'
@@ -0,0 +1,66 @@
1
+ require 'social_net/byte/api/request'
2
+ require 'social_net/byte/errors'
3
+
4
+ module SocialNet
5
+ module Byte
6
+ module Models
7
+ class Post
8
+ attr_reader :id,
9
+ :author_id,
10
+ :caption,
11
+ :category,
12
+ :mentions,
13
+ :date,
14
+ :video_src,
15
+ :thumb_src,
16
+ :comment_count,
17
+ :comments,
18
+ :like_count,
19
+ :loop_count
20
+
21
+ def initialize(attrs = {})
22
+ attrs.each{|k, v| instance_variable_set("@#{k}", v) unless v.nil?}
23
+ end
24
+
25
+ # Returns the existing Byte post matching the provided attributes or
26
+ # nil when the post is not found.
27
+ #
28
+ # @return [SocialNet::Byte::Models::post] when the post is found.
29
+ # @return [nil] when the post is not found.
30
+ # @param [Hash] params the attributes to find a post by.
31
+ # @option params [String] :id The Byte post’s id
32
+ # (case-insensitive).
33
+ def self.find_by(params = {})
34
+ find_by! params
35
+ rescue Errors::UnknownPost
36
+ nil
37
+ end
38
+
39
+ # Returns the existing Byte post matching the provided attributes or
40
+ # nil when the post is not found, and raises an error when the post is not found.
41
+ #
42
+ # @return [SocialNet::Byte::Models::Post] the Byte post.
43
+ # @param [Hash] params the attributes to find a post by.
44
+ # @option params [String] :id The Byte post id
45
+ # (case-sensitive).
46
+ # @raise [SocialNet::Errors::UnknownPost] if the post is unknown.
47
+ def self.find_by!(params = {})
48
+ if params[:id]
49
+ find_by_id! params[:id]
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def self.find_by_id!(id)
56
+ request = Api::Request.new endpoint: "/post/id/#{id}"
57
+ if post = request.run['data']
58
+ new post.deep_transform_keys { |key| key.underscore.to_sym }
59
+ else
60
+ raise Errors::UnknownPost
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,99 @@
1
+ require 'social_net/byte/api/request'
2
+ require 'social_net/byte/errors'
3
+
4
+ module SocialNet
5
+ module Byte
6
+ module Models
7
+ class User
8
+ attr_reader :id,
9
+ :avatar_url,
10
+ :bio,
11
+ :display_name,
12
+ :follower_count,
13
+ :following_count,
14
+ :username,
15
+ :registration_date
16
+
17
+ def initialize(attrs = {})
18
+ attrs.each{|k, v| instance_variable_set("@#{k}", v) unless v.nil?}
19
+ end
20
+
21
+ # Returns the existing Byte user's most recent posts
22
+ #
23
+ # @return [SocialNet::Byte::Models::Post] when the posts are found.
24
+ # @ param [Hash] params the attributes to find paginated posts by.
25
+ # @option params [String] :next_page The next page of paginated posts.
26
+ def posts(opts={})
27
+ params = {endpoint: "/account/id/#{@id}/posts"}.merge! opts
28
+ request = Api::Request.new params
29
+ posts_data = request.run
30
+ {}.tap do |k,v|
31
+ k[:posts] = posts_data['data']['posts'].map{|post| Post.new post.deep_transform_keys { |key| key.underscore.to_sym }}
32
+ k[:next_page] = posts_data['data']['cursor']
33
+ end
34
+ rescue Errors::ResponseError => error
35
+ case error.response
36
+ when Net::HTTPBadRequest then raise Errors::UnknownUser
37
+ when Net::HTTPNotFound then raise Errors::UnknownUser
38
+ end
39
+ end
40
+
41
+ # Returns the existing Byte user matching the provided attributes or
42
+ # nil when the user is not found.
43
+ #
44
+ # @return [SocialNet::Byte::Models::User] when the user is found.
45
+ # @return [nil] when the user is not found.
46
+ # @param [Hash] params the attributes to find a user by.
47
+ # @option params [String] :username The Byte user’s username
48
+ # (case-insensitive).
49
+ # @option params [String] :id The Byte user’s id
50
+ # (case-insensitive).
51
+ def self.find_by(params = {})
52
+ find_by! params
53
+ rescue Errors::UnknownUser
54
+ nil
55
+ end
56
+
57
+ # Returns the existing Byte user matching the provided attributes or
58
+ # nil when the user is not found, and raises an error when the user account is private.
59
+ #
60
+ # @return [SocialNet::Byte::Models::User] the Byte user.
61
+ # @param [Hash] params the attributes to find a user by.
62
+ # @option params [String] :username The Byte user’s username
63
+ # (case-insensitive).
64
+ # @option params [String] :id The Byte user’s id
65
+ # (case-insensitive).
66
+ # @raise [SocialNet::Errors::UnknownUser] if the user account is unknown.
67
+ def self.find_by!(params = {})
68
+ if params[:username]
69
+ find_by_username! params[:username]
70
+ elsif params[:id]
71
+ find_by_id! params[:id]
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def self.find_by_username!(username)
78
+ request = Api::Request.new endpoint: "/account/prefix/#{username}"
79
+ response = request.run
80
+ users = response['data']['accounts']
81
+ if user = users.find{|u| u['username'].casecmp(username).zero?}
82
+ new user.transform_keys { |key| key.underscore.to_sym }
83
+ else
84
+ raise Errors::UnknownUser
85
+ end
86
+ end
87
+
88
+ def self.find_by_id!(id)
89
+ request = Api::Request.new endpoint: "/account/id/#{id}"
90
+ if user = request.run['data']
91
+ new user.transform_keys { |key| key.underscore.to_sym }
92
+ else
93
+ raise Errors::UnknownUser
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ require 'social_net/byte/models/user'
2
+ require 'social_net/byte/models/post'
@@ -0,0 +1,11 @@
1
+ require 'social_net/byte/config'
2
+ require 'social_net/byte/models'
3
+ require 'social_net/byte/errors'
4
+
5
+ module SocialNet
6
+ module Byte
7
+ extend Config
8
+ include Errors
9
+ include Models
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module SocialNet
2
- VERSION = "0.2.19"
2
+ VERSION = "0.2.20"
3
3
  end
data/lib/social_net.rb CHANGED
@@ -2,6 +2,7 @@ require "social_net/version"
2
2
  require "social_net/twitter"
3
3
  require "social_net/instagram"
4
4
  require "social_net/facebook"
5
+ require "social_net/byte"
5
6
 
6
7
  module SocialNet
7
8
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'social_net/byte'
3
+
4
+ describe SocialNet::Byte::Post, :vcr do
5
+ before :all do
6
+ SocialNet::Byte.configure do |config|
7
+ if config.access_token.blank?
8
+ config.access_token = 'ACCESS_TOKEN'
9
+ end
10
+ end
11
+ end
12
+
13
+ let(:existing_post_id) { 'WZPPQ5LQJZAAHP4BWFLM6PRBNM' }
14
+ let(:nonexistant_post_id) { 'XAMM45LQJZNHHP4EWFLM6PMKIO' }
15
+
16
+ describe '.find_by id' do
17
+ subject(:post) { SocialNet::Byte::Post.find_by id: id }
18
+
19
+ context 'given an existing (case-sensitive) post id' do
20
+ let(:id) { existing_post_id }
21
+
22
+ it 'returns an object representing that post' do
23
+ expect(post.id).to eq 'WZPPQ5LQJZAAHP4BWFLM6PRBNM'
24
+ expect(post.comment_count).to be_an Integer
25
+ end
26
+ end
27
+
28
+ context 'given a non-existant post id' do
29
+ let(:id) { nonexistant_post_id }
30
+
31
+ it { expect(post).to be_nil }
32
+ end
33
+ end
34
+
35
+ describe '.find_by!' do
36
+ subject(:post) { SocialNet::Byte::Post.find_by! id: id }
37
+
38
+ context 'given an existing (case-sensitive) post id' do
39
+ let(:id) { existing_post_id }
40
+
41
+ it 'returns an object representing that post' do
42
+ expect(post.id).to eq 'WZPPQ5LQJZAAHP4BWFLM6PRBNM'
43
+ expect(post.comment_count).to be_an Integer
44
+ end
45
+ end
46
+
47
+ context 'given a non-existant post id' do
48
+ let(:id) { nonexistant_post_id }
49
+
50
+ it { expect{post}.to raise_error SocialNet::Byte::UnknownPost }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+ require 'social_net/byte'
3
+
4
+ describe SocialNet::Byte::User, :vcr do
5
+ before :all do
6
+ SocialNet::Byte.configure do |config|
7
+ if config.access_token.blank?
8
+ config.access_token = 'ACCESS_TOKEN'
9
+ end
10
+ end
11
+ end
12
+
13
+ let(:existing_username) { 'ollie' }
14
+ let(:unknown_username) { '01LjqweoojkjR' }
15
+ let(:existing_user_id_with_no_posts) { 'EGDL2NQ6WRFQRBSY6Q5D5HWKHQ' }
16
+ let(:existing_id) { 'PUEMKGYDBFAZ3HSRSAFGBAI5HA' }
17
+ let(:unknown_id) { '123456' }
18
+ let(:existing_cursor) { 'CXWZB7CTMYLTA' }
19
+
20
+ describe '.find_by username' do
21
+ subject(:user) { SocialNet::Byte::User.find_by username: username }
22
+
23
+ context 'given an existing (case-insensitive) username' do
24
+ let(:username) { existing_username }
25
+
26
+ it 'returns an object representing that user' do
27
+ expect(user.username).to eq 'ollie'
28
+ expect(user.follower_count).to be_an Integer
29
+ end
30
+ end
31
+
32
+ context 'given an unknown username' do
33
+ let(:username) { unknown_username }
34
+ it { expect(user).to be_nil }
35
+ end
36
+ end
37
+
38
+ describe '.find_by id' do
39
+ subject(:user) { SocialNet::Byte::User.find_by id: id }
40
+
41
+ context 'given an existing (case-sensitive) ID' do
42
+ let(:id) { existing_id }
43
+
44
+ it 'returns an object representing that user' do
45
+ expect(user.username).to eq 'ollie'
46
+ expect(user.follower_count).to be_an Integer
47
+ end
48
+ end
49
+
50
+ context 'given an unknown ID' do
51
+ let(:id) { unknown_id }
52
+ it { expect(user).to be_nil }
53
+ end
54
+ end
55
+
56
+ describe '.find_by!' do
57
+ subject(:user) { SocialNet::Byte::User.find_by! username: username }
58
+
59
+ context 'given an existing (case-sensitive) username' do
60
+ let(:username) { existing_username }
61
+
62
+ it 'returns an object representing that user' do
63
+ expect(user.username).to eq 'ollie'
64
+ expect(user.follower_count).to be_an Integer
65
+ end
66
+ end
67
+
68
+ context 'given an unknown username' do
69
+ let(:username) { unknown_username }
70
+ it { expect{user}.to raise_error SocialNet::Byte::UnknownUser }
71
+ end
72
+ end
73
+
74
+ describe '.posts' do
75
+ subject(:user) { SocialNet::Byte::User.find_by id: id }
76
+ context 'given an existing user with posts' do
77
+ let(:id) { existing_id }
78
+
79
+ it 'returns an array of video posts from the user' do
80
+ expect(user.posts[:posts]).to be_an Array
81
+ expect(user.posts[:posts].first).to be_an_instance_of SocialNet::Byte::Post
82
+ end
83
+ end
84
+
85
+ context 'given an existing user with no posts' do
86
+ let(:id) { existing_user_id_with_no_posts }
87
+
88
+ it 'returns an empty array from the user' do
89
+ expect(user.posts[:posts]).to be_an Array
90
+ expect(user.posts[:posts]).to be_empty
91
+ end
92
+ end
93
+
94
+ context 'given an existing user with paginated posts' do
95
+ let(:id) { existing_id }
96
+ let(:next_page) { existing_cursor }
97
+
98
+ it 'returns an array of video posts from the user' do
99
+ expect(user.posts({next_page: next_page})[:posts]).to be_an Array
100
+ expect(user.posts({next_page: next_page})[:posts].first).to be_an_instance_of SocialNet::Byte::Post
101
+ expect(user.posts({next_page: next_page})[:next_page]).not_to eq next_page
102
+ end
103
+ end
104
+ end
105
+ end