yt 0.6.1 → 0.6.2

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 (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