yt 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/HISTORY.md +3 -0
  5. data/README.md +5 -1
  6. data/TODO.md +0 -2
  7. data/bin/yt +2 -0
  8. data/lib/yt/actions/delete_all.rb +2 -2
  9. data/lib/yt/associations/authentications.rb +17 -2
  10. data/lib/yt/collections/base.rb +6 -0
  11. data/lib/yt/collections/statuses.rb +22 -0
  12. data/lib/yt/collections/subscriptions.rb +1 -1
  13. data/lib/yt/collections/videos.rb +6 -1
  14. data/lib/yt/models/account.rb +6 -12
  15. data/lib/yt/models/base.rb +28 -2
  16. data/lib/yt/models/channel.rb +37 -2
  17. data/lib/yt/models/playlist.rb +24 -0
  18. data/lib/yt/models/video.rb +19 -0
  19. data/lib/yt/version.rb +1 -1
  20. data/spec/associations/device_auth/account_spec.rb +31 -0
  21. data/spec/associations/device_auth/channel_spec.rb +114 -0
  22. data/spec/associations/device_auth/content_owner_spec.rb +8 -0
  23. data/spec/associations/device_auth/earnings_spec.rb +2 -0
  24. data/spec/associations/device_auth/playlist_spec.rb +136 -0
  25. data/spec/associations/device_auth/{ids_spec.rb → resource_spec.rb} +2 -2
  26. data/spec/associations/device_auth/video_spec.rb +42 -0
  27. data/spec/associations/no_auth/video_spec.rb +13 -0
  28. data/spec/associations/server_auth/channel_spec.rb +50 -0
  29. data/spec/associations/server_auth/playlist_spec.rb +36 -0
  30. data/spec/associations/server_auth/{ids_spec.rb → resource_spec.rb} +2 -2
  31. data/spec/associations/server_auth/video_spec.rb +22 -0
  32. data/spec/models/channel_spec.rb +7 -0
  33. metadata +25 -60
  34. data/lib/yt/associations.rb +0 -38
  35. data/lib/yt/associations/annotations.rb +0 -15
  36. data/lib/yt/associations/channels.rb +0 -20
  37. data/lib/yt/associations/details_sets.rb +0 -20
  38. data/lib/yt/associations/ids.rb +0 -20
  39. data/lib/yt/associations/partnered_channels.rb +0 -14
  40. data/lib/yt/associations/playlist_items.rb +0 -34
  41. data/lib/yt/associations/playlists.rb +0 -22
  42. data/lib/yt/associations/ratings.rb +0 -39
  43. data/lib/yt/associations/snippets.rb +0 -20
  44. data/lib/yt/associations/statuses.rb +0 -14
  45. data/lib/yt/associations/subscriptions.rb +0 -34
  46. data/lib/yt/associations/user_infos.rb +0 -21
  47. data/lib/yt/associations/videos.rb +0 -14
  48. data/spec/associations/device_auth/channels_spec.rb +0 -8
  49. data/spec/associations/device_auth/details_sets_spec.rb +0 -18
  50. data/spec/associations/device_auth/partnered_channels_spec.rb +0 -15
  51. data/spec/associations/device_auth/playlist_items_spec.rb +0 -79
  52. data/spec/associations/device_auth/playlists_spec.rb +0 -61
  53. data/spec/associations/device_auth/ratings_spec.rb +0 -28
  54. data/spec/associations/device_auth/snippets_spec.rb +0 -28
  55. data/spec/associations/device_auth/subscriptions_spec.rb +0 -35
  56. data/spec/associations/device_auth/user_infos_spec.rb +0 -12
  57. data/spec/associations/device_auth/videos_spec.rb +0 -20
  58. data/spec/associations/no_auth/annotations_spec.rb +0 -15
  59. data/spec/associations/server_auth/channels_spec.rb +0 -2
  60. data/spec/associations/server_auth/details_sets_spec.rb +0 -18
  61. data/spec/associations/server_auth/playlist_items_spec.rb +0 -17
  62. data/spec/associations/server_auth/playlists_spec.rb +0 -17
  63. data/spec/associations/server_auth/ratings_spec.rb +0 -2
  64. data/spec/associations/server_auth/snippets_spec.rb +0 -28
  65. data/spec/associations/server_auth/subscriptions_spec.rb +0 -2
  66. data/spec/associations/server_auth/user_infos_spec.rb +0 -2
  67. data/spec/associations/server_auth/videos_spec.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed6567aa70134861a5d5267d15be4492828e43f3
4
- data.tar.gz: d4bdab518b596e713bd176aec6b8433717f33770
3
+ metadata.gz: c4e025834f30276ff685e3ff61e3bd1330fc42b4
4
+ data.tar.gz: 725a5254897a04ff2ff0694125b58101dd6689aa
5
5
  SHA512:
6
- metadata.gz: 06c0762ba2dad8a2cef8e02484e8b1640c83b247e1f3137dbf13fdd7822be95602cdcb6aab7adba763bd69e853c86727daa171c8491ba55922d6098e17585202
7
- data.tar.gz: 54c741b87f443665ba64a14133d2816616ae0f3abc82794e42a2233e6ea7534a78e658cc3128cae5d324001f68bf2485764f9d61f2baf866331ecf486f754dce
6
+ metadata.gz: 582c36cde9977d2d0b7585fea15950ee1f09292ed373e39a9b99f53ae8ce32a8bdb198106d8afe00d78c60f28890eab1d9ec81f2ba5d6e9495fbc9f60c35943d
7
+ data.tar.gz: 3e9bb4cda3a7fec3d604c92d3c8d609b7463a20a8e833cb01ff8b8c09e40b0bf05e106d35bfc68d76a7655e6b3095e09e650244c1293555e45ae54de2175deba
@@ -22,3 +22,4 @@ env:
22
22
  - secure: Ejj8tsuwyrRVmCc/R9ubKWCHWhCGpe0Dy6fc1UuPCkcMZyXq9ZC02v2obWsTQQ7epEgsCYZAO4v/gWpuv1b1huGcWdfJzMW7RCoY87cEf9HnAK0lSwGx4+/pYkEMe8y5p149C3vAR8nqczvEavN1fUq/WwPUqp+JyDP7kwFTs2Y=
23
23
  - secure: gE5kAT1R54hmS+W3YYGcUtlD8ZskvTctVR3sr+C5CUjVPdq6Ktx5Q/a6EJyAVVrhxpaCOuk3LG+VkzdQIVFUNRiDPcOulkond4HkSQDoy+IJ/wTXvUS+lIJ1ERUnWega+APrQUjH5s2WayPGZUBqWt/u8Tt9EmSUZfuKZSEXqZk=
24
24
  - secure: ZUx5v/wHW/TENg8NfFINiiMoe2D031ntDTiuIBdf88c/bMClkEtRRgomtK9RBkFonEyGEOkXxUm2SLzRf340V3eIXWQhil7ab1lcYs8X59aVS/NK/GqChH8Nia17gc3OTQ9k6rYvj4Lp60Dh9WG1cijLPd4/OvPmf6qX9uYfJMw=
25
+ - secure: DumQVO01Y3Ki1skuOYOZzosDb6jS0XyG1O8Agy3mVxXGJzQE+s1z2UFz4gMpsU9o/gmiNMddp7I6+RtbZjo9hN3H7vlRRwEeB7tuUMiDyomSx1FlHcCFfPdTmhxGg8X78SErMWqNC6eReGrCTgBdIq1ho7dIu53qJNxTEFqx7eI=
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- yt (0.6.1)
4
+ yt (0.6.2)
5
5
  activesupport
6
6
 
7
7
  GEM
data/HISTORY.md CHANGED
@@ -2,6 +2,9 @@ v0.6 - 2014/06/05
2
2
  -----------------
3
3
 
4
4
  * [breaking change] Rename Channel#earning to Channel#earnings_on
5
+ * [breaking change] Account#videos shows *all* videos owned by account (public and private)
6
+ * Add the .status association to *every* type of resource (Channel, Video, Playlist)
7
+ * Allow account.videos to be chained with .where, such as in account.videos.where(q: 'query')
5
8
 
6
9
  v0.5 - 2014/05/16
7
10
  -----------------
data/README.md CHANGED
@@ -16,12 +16,14 @@ After [registering your app](#configuring-your-app), you can run commands like:
16
16
  channel = Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow'
17
17
  channel.title #=> "Fullscreen"
18
18
  channel.description #=> "The first media company for the connected generation."
19
+ channel.public? #=> true
19
20
  channel.videos.count #=> 12
20
21
  ```
21
22
 
22
23
  ```ruby
23
24
  video = Yt::Video.new id: 'MESycYJytkU'
24
25
  video.title #=> "Fullscreen Creator Platform"
26
+ video.public? #=> true
25
27
  video.duration #=> 86
26
28
  video.annotations.count #=> 1
27
29
  ```
@@ -84,6 +86,7 @@ Use [Yt::Channel](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Chan
84
86
  channel = Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow'
85
87
  channel.title #=> "Fullscreen"
86
88
  channel.description.has_link_to_playlist? #=> false
89
+ channel.public? #=> true
87
90
 
88
91
  channel.videos.count #=> 12
89
92
  channel.videos.first #=> #<Yt::Video @id=...>
@@ -136,6 +139,7 @@ video = Yt::Video.new id: 'MESycYJytkU'
136
139
  video.title #=> "Fullscreen Creator Platform"
137
140
  video.duration #=> 63
138
141
  video.description.has_link_to_subscribe? #=> false
142
+ video.public? #=> true
139
143
 
140
144
  video.annotations.count #=> 1
141
145
  video.annotations.first #=> #<Yt::Annotation @id=...>
@@ -326,7 +330,7 @@ To install on your system, run
326
330
 
327
331
  To use inside a bundled Ruby project, add this line to the Gemfile:
328
332
 
329
- gem 'yt', '~> 0.6.1'
333
+ gem 'yt', '~> 0.6.2'
330
334
 
331
335
  Since the gem follows [Semantic Versioning](http://semver.org),
332
336
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
data/TODO.md CHANGED
@@ -8,8 +8,6 @@ seconds fixes it, so we should retry every 500 at least
8
8
  * Google accounts?
9
9
  * ENV support
10
10
 
11
- * add has_one :status to video
12
-
13
11
  * operations like subscribe that require authentication should not fail if
14
12
  called on Yt::Channel without auth but, similarly to account, show the prompt
15
13
  or ask for the device code
data/bin/yt CHANGED
@@ -14,6 +14,7 @@ channel = Yt::Channel.new id: ARGV[0] || 'UCxO1tY8h1AhOz0T4ENwmpow'
14
14
  puts "Title: #{channel.title}"
15
15
  puts "Description: #{channel.description}"
16
16
  puts "Thumbnail: #{channel.thumbnail_url}"
17
+ puts "Public? #{channel.public?}"
17
18
  puts "Videos: "
18
19
  channel.videos.each do |video|
19
20
  puts " Annotations: #{video.annotations.count}"
@@ -21,4 +22,5 @@ channel.videos.each do |video|
21
22
  puts " Title: #{video.title}"
22
23
  puts " Description: #{video.description}"
23
24
  puts " Thumbnail: #{video.thumbnail_url}"
25
+ puts " Public? #{video.public?}"
24
26
  end
@@ -9,12 +9,12 @@ module Yt
9
9
  private
10
10
 
11
11
  def do_delete_all(params = {})
12
- where(params).map do |item|
12
+ list_all(params).map do |item|
13
13
  item.delete
14
14
  end.tap { @items = [] }
15
15
  end
16
16
 
17
- def where(params = {})
17
+ def list_all(params = {})
18
18
  list.find_all do |item|
19
19
  params.all? do |method, value|
20
20
  # TODO: could be symbol etc...
@@ -5,10 +5,25 @@ require 'yt/errors/unauthorized'
5
5
 
6
6
  module Yt
7
7
  module Associations
8
- # Provides the `has_one :access_token` method to YouTube resources, which
8
+ # Provides authentication methods to YouTube resources, which
9
9
  # allows to access to content detail set-specific methods like `access_token`.
10
- # YouTube resources with access tokens are: accounts.
10
+ # YouTube resources with authentication are: accounts.
11
11
  module Authentications
12
+ delegate :access_token, :refresh_token, :expires_at, to: :authentication
13
+
14
+ def initialize(options = {})
15
+ @access_token = options[:access_token]
16
+ @refresh_token = options[:refresh_token]
17
+ @expires_at = options[:expires_at]
18
+ @authorization_code = options[:authorization_code]
19
+ @redirect_uri = options[:redirect_uri]
20
+ @scopes = options[:scopes]
21
+ end
22
+
23
+ def auth
24
+ self
25
+ end
26
+
12
27
  def authentication
13
28
  @authentication = current_authentication
14
29
  @authentication ||= new_authentication || refreshed_authentication!
@@ -18,6 +18,12 @@ module Yt
18
18
  def self.of(parent)
19
19
  new parent: parent, auth: parent.auth
20
20
  end
21
+
22
+ def where(conditions = {})
23
+ @items = []
24
+ @extra_params = conditions
25
+ self
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -0,0 +1,22 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/status'
3
+
4
+ module Yt
5
+ module Collections
6
+ class Statuses < Base
7
+
8
+ private
9
+
10
+ def new_item(data)
11
+ Yt::Status.new data: data['status']
12
+ end
13
+
14
+ def list_params
15
+ super.tap do |params|
16
+ params[:params] = {id: @parent.id, part: 'status'}
17
+ params[:path] = "/youtube/v3/#{@parent.kind.pluralize}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -29,7 +29,7 @@ module Yt
29
29
  # To overcome this, if we have just updated the subscription, we must
30
30
  # wait some time before requesting it again.
31
31
  #
32
- def throttle(seconds = 10)
32
+ def throttle(seconds = 11)
33
33
  @last_changed_at ||= Time.now - seconds
34
34
  wait = [@last_changed_at - Time.now + seconds, 0].max
35
35
  sleep wait
@@ -13,10 +13,15 @@ module Yt
13
13
 
14
14
  def list_params
15
15
  super.tap do |params|
16
- params[:params] = {channelId: @parent.id, type: :video, maxResults: 50, part: 'snippet'}
16
+ params[:params] = @parent.videos_params.merge videos_params
17
17
  params[:path] = '/youtube/v3/search'
18
18
  end
19
19
  end
20
+
21
+ def videos_params
22
+ @extra_params ||= {}
23
+ {type: :video, maxResults: 50, part: 'snippet'}.merge @extra_params
24
+ end
20
25
  end
21
26
  end
22
27
  end
@@ -1,24 +1,18 @@
1
1
  require 'yt/models/base'
2
+ require 'yt/associations/authentications'
2
3
 
3
4
  module Yt
4
5
  module Models
5
6
  # Provides methods to access a YouTube account.
6
7
  class Account < Base
8
+ include Associations::Authentications
9
+
7
10
  has_one :channel, delegate: [:videos, :playlists, :create_playlist, :delete_playlists, :update_playlists]
8
11
  has_one :user_info, delegate: [:id, :email, :has_verified_email?, :gender, :name, :given_name, :family_name, :profile_url, :avatar_url, :locale, :hd]
9
- has_one :authentication, delegate: [:access_token, :refresh_token, :expires_at]
10
-
11
- def initialize(options = {})
12
- @access_token = options[:access_token]
13
- @refresh_token = options[:refresh_token]
14
- @expires_at = options[:expires_at]
15
- @authorization_code = options[:authorization_code]
16
- @redirect_uri = options[:redirect_uri]
17
- @scopes = options[:scopes]
18
- end
12
+ has_many :videos
19
13
 
20
- def auth
21
- self
14
+ def videos_params
15
+ {forMine: true}
22
16
  end
23
17
  end
24
18
  end
@@ -1,14 +1,40 @@
1
- require 'yt/associations'
2
1
  require 'yt/actions/delete'
3
2
  require 'yt/actions/update'
4
3
  require 'yt/errors/request_error'
5
4
 
5
+ require 'active_support/core_ext/module/delegation' # for delegate
6
+ require 'active_support/core_ext/string/inflections' # for camelize
7
+
8
+
6
9
  module Yt
7
10
  module Models
8
11
  class Base
9
- extend Associations
10
12
  include Actions::Delete
11
13
  include Actions::Update
14
+
15
+ def self.has_many(attributes)
16
+ attributes = attributes.to_s
17
+ require "yt/collections/#{attributes}"
18
+ mod = attributes.sub(/.*\./, '').camelize
19
+ collection = "Yt::Collections::#{mod.pluralize}".constantize
20
+
21
+ define_method attributes do
22
+ ivar = instance_variable_get "@#{attributes}"
23
+ instance_variable_set "@#{attributes}", ivar || collection.of(self)
24
+ end
25
+ end
26
+
27
+ def self.has_one(attribute, options = {})
28
+ delegate *options[:delegate], to: attribute if options[:delegate]
29
+
30
+ attributes = attribute.to_s.pluralize
31
+ has_many attributes
32
+
33
+ define_method attribute do
34
+ ivar = instance_variable_get "@#{attribute}"
35
+ instance_variable_set "@#{attribute}", ivar || send(attributes).first!
36
+ end
37
+ end
12
38
  end
13
39
  end
14
40
 
@@ -1,13 +1,48 @@
1
1
  require 'yt/models/resource'
2
+ require 'yt/associations/earnings'
3
+ require 'yt/associations/views'
2
4
 
3
5
  module Yt
4
6
  module Models
5
7
  class Channel < Resource
8
+ include Associations::Earnings
9
+ include Associations::Views
10
+
6
11
  has_many :subscriptions
7
12
  has_many :videos
8
13
  has_many :playlists
9
- has_many :earnings
10
- has_many :views
14
+
15
+ def subscribed?
16
+ subscriptions.any?{|s| s.exists?}
17
+ end
18
+
19
+ def subscribe
20
+ subscriptions.insert ignore_errors: true
21
+ end
22
+
23
+ def subscribe!
24
+ subscriptions.insert
25
+ end
26
+
27
+ def unsubscribe
28
+ subscriptions.delete_all({}, ignore_errors: true)
29
+ end
30
+
31
+ def unsubscribe!
32
+ subscriptions.delete_all
33
+ end
34
+
35
+ def create_playlist(params = {})
36
+ playlists.insert params
37
+ end
38
+
39
+ def delete_playlists(attrs = {})
40
+ playlists.delete_all attrs
41
+ end
42
+
43
+ def videos_params
44
+ {channelId: id}
45
+ end
11
46
  end
12
47
  end
13
48
  end
@@ -32,6 +32,26 @@ module Yt
32
32
  !@id.nil?
33
33
  end
34
34
 
35
+ def add_video(video_id)
36
+ playlist_items.insert video_params(video_id), ignore_errors: true
37
+ end
38
+
39
+ def add_video!(video_id)
40
+ playlist_items.insert video_params(video_id)
41
+ end
42
+
43
+ def add_videos(video_ids = [])
44
+ video_ids.map{|video_id| add_video video_id}
45
+ end
46
+
47
+ def add_videos!(video_ids = [])
48
+ video_ids.map{|video_id| add_video! video_id}
49
+ end
50
+
51
+ def delete_playlist_items(attrs = {})
52
+ playlist_items.delete_all attrs
53
+ end
54
+
35
55
  private
36
56
 
37
57
  def delete_params
@@ -48,6 +68,10 @@ module Yt
48
68
  params[:expected_response] = Net::HTTPOK
49
69
  end
50
70
  end
71
+
72
+ def video_params(video_id)
73
+ {id: video_id, kind: :video}
74
+ end
51
75
  end
52
76
  end
53
77
  end
@@ -6,6 +6,25 @@ module Yt
6
6
  has_one :details_set, delegate: [:duration]
7
7
  has_one :rating
8
8
  has_many :annotations
9
+
10
+ def liked?
11
+ rating.rating == :like
12
+ end
13
+
14
+ def like
15
+ rating.update :like
16
+ liked?
17
+ end
18
+
19
+ def dislike
20
+ rating.update :dislike
21
+ !liked?
22
+ end
23
+
24
+ def unlike
25
+ rating.update :none
26
+ !liked?
27
+ end
9
28
  end
10
29
  end
11
30
  end
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.6.1'
2
+ VERSION = '0.6.2'
3
3
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/account'
3
+
4
+ describe Yt::Account, :device_app do
5
+ describe '.channel' do
6
+ it { expect($account.channel).to be_a Yt::Channel }
7
+ end
8
+
9
+ describe '.user_info' do
10
+ it { expect($account.user_info).to be_a Yt::UserInfo }
11
+ end
12
+
13
+ describe '.videos' do
14
+ it { expect($account.videos).to be_a Yt::Collections::Videos }
15
+ it { expect($account.videos.first).to be_a Yt::Video }
16
+
17
+ describe '.where(q: query_string)' do
18
+ let(:count) { $account.videos.where(q: query).count }
19
+
20
+ context 'given a query string that matches any video owned by the account' do
21
+ let(:query) { ENV['YT_TEST_MATCHING_QUERY_STRING'] }
22
+ it { expect(count).to be > 0 }
23
+ end
24
+
25
+ context 'given a query string that does not match any video owned by the account' do
26
+ let(:query) { '--not-a-matching-query-string--' }
27
+ it { expect(count).to be_zero }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'yt/models/channel'
5
+
6
+ describe Yt::Channel, :device_app do
7
+ let(:channel) { Yt::Channel.new id: id, auth: $account }
8
+
9
+ describe '.snippet of existing channel' do
10
+ let(:id) { 'UCxO1tY8h1AhOz0T4ENwmpow' }
11
+ it { expect(channel.snippet).to be_a Yt::Snippet }
12
+ end
13
+
14
+ describe '.snippet of unknown channel' do
15
+ let(:id) { 'not-a-channel-id' }
16
+ it { expect{channel.snippet}.to raise_error Yt::Errors::NoItems }
17
+ end
18
+
19
+ describe '.status of existing channel' do
20
+ let(:id) { 'UCxO1tY8h1AhOz0T4ENwmpow' }
21
+ it { expect(channel.status).to be_a Yt::Status }
22
+ end
23
+
24
+ describe '.status of unknown channel' do
25
+ let(:id) { 'not-a-channel-id' }
26
+ it { expect{channel.status}.to raise_error Yt::Errors::NoItems }
27
+ end
28
+
29
+ describe '.videos of existing channel' do
30
+ let(:id) { 'UCxO1tY8h1AhOz0T4ENwmpow' }
31
+ it { expect(channel.videos).to be_a Yt::Collections::Videos }
32
+ end
33
+
34
+ describe '.videos of unknown channel starting with UC' do
35
+ let(:id) { 'UC-not-a-channel-id' }
36
+
37
+ # NOTE: This test is just a reflection of YouTube irrational behavior of
38
+ # returns 0 results if the name of an unknown channel starts with UC, but
39
+ # returning 100,000 results otherwise (ignoring the channel filter).
40
+ it { expect(channel.videos.count).to be 0 }
41
+ end
42
+
43
+ describe '.subscriptions to an existing channel' do
44
+ let(:id) { 'UCxO1tY8h1AhOz0T4ENwmpow' }
45
+ it { expect(channel.subscriptions).to be_a Yt::Collections::Subscriptions }
46
+
47
+ # NOTE: These tests are slow because we *must* wait some seconds between
48
+ # subscribing and unsubscribing to a channel, otherwise YouTube will show
49
+ # wrong (cached) data, such as a user is subscribed when he is not.
50
+ context 'can be added', :slow do
51
+ before { channel.unsubscribe }
52
+ it { expect(channel.subscribed?).to be false }
53
+ it { expect(channel.subscribe!).to be_truthy }
54
+ end
55
+
56
+ context 'can be removed', :slow do
57
+ before { channel.subscribe }
58
+ it { expect(channel.subscribed?).to be true }
59
+ it { expect(channel.unsubscribe!).to be_truthy }
60
+ end
61
+ end
62
+
63
+ describe '.subscriptions to an unknown channel' do
64
+ let(:id) { 'not-a-channel-id' }
65
+ it { expect{channel.subscribe}.to raise_error Yt::Errors::RequestError }
66
+ end
67
+
68
+ describe '.subscriptions to my own channel' do
69
+ let(:id) { $account.channel.id }
70
+
71
+ # NOTE: This test is just a reflection of YouTube irrational behavior of
72
+ # raising a 500 error when you try to subscribe to your own channel, rather
73
+ # than a more logical 4xx error. Hopefully this will get fixed and this
74
+ # code (and test) removed.
75
+ it { expect{channel.subscribe}.to raise_error Yt::Errors::ServerError }
76
+ end
77
+
78
+ describe '.playlists of my own channel' do
79
+ let(:id) { $account.channel.id }
80
+ let(:title) { 'Yt Test title' }
81
+ let(:description) { 'Yt Test description' }
82
+ let(:tags) { ['Yt Test Tag 1', 'Yt Test Tag 2'] }
83
+ let(:privacy_status) { 'unlisted' }
84
+ let(:params) { {title: title, description: description, tags: tags, privacy_status: privacy_status} }
85
+
86
+ it { expect(channel.playlists).to be_a Yt::Collections::Playlists }
87
+
88
+ describe 'can be created' do
89
+ after { channel.delete_playlists params }
90
+ it { expect(channel.create_playlist params).to be_a Yt::Playlist }
91
+ it { expect{channel.create_playlist params}.to change{channel.playlists.count}.by(1) }
92
+ end
93
+
94
+ describe 'can be deleted' do
95
+ let(:title) { "Yt Test Delete All Playlists #{rand}" }
96
+ before { channel.create_playlist params }
97
+
98
+ it { expect(channel.delete_playlists title: %r{#{params[:title]}}).to eq [true] }
99
+ it { expect(channel.delete_playlists params).to eq [true] }
100
+ it { expect{channel.delete_playlists params}.to change{channel.playlists.count}.by(-1) }
101
+ end
102
+ end
103
+
104
+ describe '.playlists of someone else’s channel' do
105
+ let(:id) { 'UCxO1tY8h1AhOz0T4ENwmpow' }
106
+
107
+ it { expect(channel.playlists).to be_a Yt::Collections::Playlists }
108
+
109
+ describe 'cannot be created or destroyed' do
110
+ it { expect{channel.create_playlist}.to raise_error Yt::Errors::RequestError }
111
+ it { expect{channel.delete_playlists}.to raise_error Yt::Errors::RequestError }
112
+ end
113
+ end
114
+ end