social_net 0.2.19 → 0.2.20

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