yt 0.25.13 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +305 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +86 -5
  5. data/YOUTUBE_IT.md +3 -3
  6. data/lib/yt.rb +5 -2
  7. data/lib/yt/actions/list.rb +3 -3
  8. data/lib/yt/associations/has_authentication.rb +33 -1
  9. data/lib/yt/associations/has_reports.rb +13 -18
  10. data/lib/yt/collections/assets.rb +2 -2
  11. data/lib/yt/collections/authentications.rb +9 -2
  12. data/lib/yt/collections/base.rb +3 -3
  13. data/lib/yt/collections/bulk_report_jobs.rb +28 -0
  14. data/lib/yt/collections/bulk_reports.rb +24 -0
  15. data/lib/yt/collections/claims.rb +22 -1
  16. data/lib/yt/collections/comment_threads.rb +41 -0
  17. data/lib/yt/collections/content_owners.rb +1 -1
  18. data/lib/yt/collections/group_infos.rb +27 -0
  19. data/lib/yt/collections/group_items.rb +45 -0
  20. data/lib/yt/collections/reports.rb +75 -13
  21. data/lib/yt/collections/revocations.rb +30 -0
  22. data/lib/yt/collections/video_groups.rb +29 -0
  23. data/lib/yt/collections/videos.rb +34 -9
  24. data/lib/yt/constants/geography.rb +326 -0
  25. data/lib/yt/errors/forbidden.rb +1 -3
  26. data/lib/yt/errors/no_items.rb +1 -3
  27. data/lib/yt/errors/request_error.rb +10 -7
  28. data/lib/yt/errors/server_error.rb +1 -3
  29. data/lib/yt/errors/unauthorized.rb +3 -3
  30. data/lib/yt/models/account.rb +12 -0
  31. data/lib/yt/models/advertising_options_set.rb +4 -4
  32. data/lib/yt/models/bulk_report.rb +23 -0
  33. data/lib/yt/models/bulk_report_job.rb +23 -0
  34. data/lib/yt/models/channel.rb +21 -12
  35. data/lib/yt/models/claim.rb +13 -2
  36. data/lib/yt/models/comment.rb +37 -0
  37. data/lib/yt/models/comment_thread.rb +50 -0
  38. data/lib/yt/models/content_detail.rb +6 -0
  39. data/lib/yt/models/content_owner.rb +31 -1
  40. data/lib/yt/models/group_info.rb +16 -0
  41. data/lib/yt/models/group_item.rb +15 -0
  42. data/lib/yt/models/resource.rb +3 -10
  43. data/lib/yt/models/revocation.rb +12 -0
  44. data/lib/yt/models/right_owner.rb +0 -2
  45. data/lib/yt/models/snippet.rb +24 -3
  46. data/lib/yt/models/video.rb +42 -11
  47. data/lib/yt/models/video_group.rb +186 -0
  48. data/lib/yt/request.rb +5 -3
  49. data/lib/yt/version.rb +2 -2
  50. data/spec/collections/comment_threads_spec.rb +46 -0
  51. data/spec/collections/playlist_items_spec.rb +1 -1
  52. data/spec/collections/reports_spec.rb +2 -2
  53. data/spec/constants/geography_spec.rb +16 -0
  54. data/spec/models/annotation_spec.rb +1 -1
  55. data/spec/models/claim_spec.rb +15 -3
  56. data/spec/models/comment_spec.rb +40 -0
  57. data/spec/models/comment_thread_spec.rb +93 -0
  58. data/spec/models/content_detail_spec.rb +7 -0
  59. data/spec/models/reference_spec.rb +2 -2
  60. data/spec/models/request_spec.rb +21 -0
  61. data/spec/models/resource_spec.rb +0 -15
  62. data/spec/models/video_spec.rb +1 -1
  63. data/spec/requests/as_account/account_spec.rb +16 -4
  64. data/spec/requests/as_account/authentications_spec.rb +1 -13
  65. data/spec/requests/as_account/channel_spec.rb +15 -45
  66. data/spec/requests/as_account/playlist_item_spec.rb +3 -3
  67. data/spec/requests/as_account/playlist_spec.rb +5 -32
  68. data/spec/requests/as_account/video_spec.rb +2022 -21
  69. data/spec/requests/as_content_owner/account_spec.rb +4 -0
  70. data/spec/requests/as_content_owner/bulk_report_job_spec.rb +19 -0
  71. data/spec/requests/as_content_owner/channel_spec.rb +59 -270
  72. data/spec/requests/as_content_owner/content_owner_spec.rb +89 -1
  73. data/spec/requests/as_content_owner/playlist_spec.rb +0 -15
  74. data/spec/requests/as_content_owner/video_group_spec.rb +112 -0
  75. data/spec/requests/as_content_owner/video_spec.rb +72 -146
  76. data/spec/requests/as_server_app/channel_spec.rb +1 -21
  77. data/spec/requests/as_server_app/comment_spec.rb +22 -0
  78. data/spec/requests/as_server_app/comment_thread_spec.rb +27 -0
  79. data/spec/requests/as_server_app/comment_threads_spec.rb +41 -0
  80. data/spec/requests/as_server_app/playlist_item_spec.rb +2 -2
  81. data/spec/requests/as_server_app/playlist_spec.rb +1 -22
  82. data/spec/requests/as_server_app/video_spec.rb +21 -19
  83. data/spec/requests/as_server_app/videos_spec.rb +5 -5
  84. data/spec/requests/unauthenticated/video_spec.rb +1 -9
  85. data/spec/spec_helper.rb +1 -1
  86. data/yt.gemspec +2 -1
  87. metadata +51 -17
  88. data/lib/yt/collections/ids.rb +0 -27
  89. data/lib/yt/config.rb +0 -54
  90. data/lib/yt/models/configuration.rb +0 -70
  91. data/lib/yt/models/description.rb +0 -58
  92. data/lib/yt/models/url.rb +0 -91
  93. data/spec/models/configuration_spec.rb +0 -44
  94. data/spec/models/description_spec.rb +0 -94
  95. data/spec/models/url_spec.rb +0 -84
  96. data/spec/requests/as_account/resource_spec.rb +0 -18
@@ -197,7 +197,7 @@ module Yt
197
197
  # for a couple of seconds might solve the connection issues.
198
198
  def run_again?
199
199
  refresh_token_and_retry? ||
200
- server_error? && sleep_and_retry? ||
200
+ server_error? && sleep_and_retry?(3) ||
201
201
  exceeded_quota? && sleep_and_retry?(3)
202
202
  end
203
203
 
@@ -209,6 +209,8 @@ module Yt
209
209
  Errno::EHOSTUNREACH,
210
210
  Errno::ENETUNREACH,
211
211
  Errno::ECONNRESET,
212
+ Net::OpenTimeout,
213
+ SocketError,
212
214
  Net::HTTPServerError
213
215
  ] + extra_server_errors
214
216
  end
@@ -232,7 +234,7 @@ module Yt
232
234
  @retries_so_far += 1
233
235
  if (@retries_so_far < max_retries)
234
236
  @response = @http_request = @uri = nil
235
- sleep 3
237
+ sleep 3 + (10 * @retries_so_far)
236
238
  end
237
239
  end
238
240
 
@@ -267,7 +269,7 @@ module Yt
267
269
 
268
270
  # @return [Boolean] whether the request exceeds the YouTube quota
269
271
  def exceeded_quota?
270
- response_error == Errors::Forbidden && response.body =~ /quotaExceeded/
272
+ response_error == Errors::Forbidden && response.body =~ /Exceeded/i
271
273
  end
272
274
 
273
275
  # @return [Boolean] whether the request lacks proper authorization.
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.25.13'
3
- end
2
+ VERSION = '0.32.2'
3
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'yt/collections/comment_threads'
3
+ require 'yt/models/video'
4
+ require 'yt/models/channel'
5
+
6
+ describe Yt::Collections::CommentThreads do
7
+ subject(:collection) { Yt::Collections::CommentThreads.new parent: parent}
8
+
9
+ describe '#size', :ruby2 do
10
+ describe 'sends only one request and return the total results' do
11
+ let(:total_results) { 1234 }
12
+ let(:parent) { Yt::Video.new id: 'any-id' }
13
+
14
+ before do
15
+ expect_any_instance_of(Yt::Request).to receive(:run).once do
16
+ double(body: {'pageInfo'=>{'totalResults'=>total_results}})
17
+ end
18
+ end
19
+ it { expect(collection.size).to be total_results }
20
+ end
21
+ end
22
+
23
+ describe '#count' do
24
+ let(:query) { {q: 'search string'} }
25
+ let(:parent) { Yt::Video.new id: 'any-id' }
26
+ let(:page) { {items: [], token: 'any-token'} }
27
+
28
+ context 'called once with .where(query) and once without' do
29
+ after do
30
+ collection.where(query).count
31
+ collection.count
32
+ end
33
+
34
+ it 'only applies the query on the first call' do
35
+ expect(collection).to receive(:fetch_page) do |options|
36
+ expect(options[:params]).to include query
37
+ page
38
+ end
39
+ expect(collection).to receive(:fetch_page) do |options|
40
+ expect(options[:params]).not_to include query
41
+ page
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -6,7 +6,7 @@ require 'yt/collections/playlist_items'
6
6
  describe Yt::Collections::PlaylistItems do
7
7
  subject(:collection) { Yt::Collections::PlaylistItems.new parent: playlist }
8
8
  let(:playlist) { Yt::Playlist.new id: 'LLxO1tY8h1AhOz0T4ENwmpow' }
9
- let(:attrs) { {id: 'MESycYJytkU', kind: :video} }
9
+ let(:attrs) { {id: '9bZkp7q19f0', kind: :video} }
10
10
  let(:msg) { {response_body: {error: {errors: [{reason: reason}]}}}.to_json }
11
11
  before { expect(collection).to behave }
12
12
 
@@ -9,7 +9,7 @@ describe Yt::Collections::Reports do
9
9
  let(:msg) { {response_body: {error: {errors: [error]}}}.to_json }
10
10
 
11
11
  describe '#within' do
12
- let(:result) { reports.within Range.new(5.days.ago, 4.days.ago), nil, nil, :day, nil }
12
+ let(:result) { reports.within Range.new(5.days.ago, 4.days.ago), nil, nil, :day, nil, nil }
13
13
  context 'given the request raises error 400 with "Invalid Query" message' do
14
14
  let(:reason) { 'badRequest' }
15
15
  let(:message) { 'Invalid query. Query did not conform to the expectations' }
@@ -27,4 +27,4 @@ describe Yt::Collections::Reports do
27
27
  end
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'yt/constants/geography'
3
+
4
+ describe 'Yt::COUNTRIES' do
5
+ it 'returns all country codes and names' do
6
+ expect(Yt::COUNTRIES[:US]).to eq 'United States'
7
+ expect(Yt::COUNTRIES['IT']).to eq 'Italy'
8
+ end
9
+ end
10
+
11
+ describe 'Yt::US_STATES' do
12
+ it 'returns all U.S. state codes and names' do
13
+ expect(Yt::US_STATES[:CA]).to eq 'California'
14
+ expect(Yt::US_STATES['CO']).to eq 'Colorado'
15
+ end
16
+ end
@@ -86,7 +86,7 @@ describe Yt::Annotation do
86
86
  end
87
87
 
88
88
  context 'given an annotation with an embedded playlist link' do
89
- let(:xml) { '<TEXT>https://www.youtube.com/watch?v=MESycYJytkU&amp;list=LLxO1tY8h1AhOz0T4ENwmpow"</TEXT>' }
89
+ let(:xml) { '<TEXT>https://www.youtube.com/watch?v=9bZkp7q19f0&amp;list=LLxO1tY8h1AhOz0T4ENwmpow"</TEXT>' }
90
90
  it { expect(annotation).to have_link_to_playlist }
91
91
  end
92
92
 
@@ -20,8 +20,8 @@ describe Yt::Claim do
20
20
 
21
21
  describe '#video_id' do
22
22
  context 'given fetching a claim returns an videoId' do
23
- let(:data) { {"videoId"=>"MESycYJytkU"} }
24
- it { expect(claim.video_id).to eq 'MESycYJytkU' }
23
+ let(:data) { {"videoId"=>"9bZkp7q19f0"} }
24
+ it { expect(claim.video_id).to eq '9bZkp7q19f0' }
25
25
  end
26
26
  end
27
27
 
@@ -208,4 +208,16 @@ describe Yt::Claim do
208
208
  it { expect(claim.third_party?).to be false }
209
209
  end
210
210
  end
211
- end
211
+
212
+ describe '#source' do
213
+ context 'given fetching a claim returns a source' do
214
+ let(:data) { {"origin"=>{"source"=>"webUpload"}} }
215
+ it { expect(claim.source).to eq 'webUpload' }
216
+ end
217
+
218
+ context 'given fetching a claim does not return a source' do
219
+ let(:data) { {"origin"=>{}} }
220
+ it { expect(claim.source).to eq nil }
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/comment'
3
+
4
+ describe Yt::Comment do
5
+ subject(:comment) { Yt::Comment.new attrs }
6
+
7
+ describe '#snippet' do
8
+ context 'given fetching a comment returns a snippet' do
9
+ let(:attrs) { {snippet: {"videoId" => "12345"}} }
10
+ it { expect(comment.snippet).to be_a Yt::Snippet }
11
+ end
12
+ end
13
+
14
+ describe 'attributes' do
15
+ examples = {
16
+ video_id: {with: 'xyz123', without: nil},
17
+ parent_id: {with: 'abc123', without: nil},
18
+ text_display: {with: 'awesome', without: nil},
19
+ author_display_name: {with: 'John', without: nil},
20
+ like_count: {with: 10, without: nil},
21
+ updated_at: {input: '2016-03-22T12:56:56.3Z', with: Time.parse('2016-03-22T12:56:56.3Z'), without: nil},
22
+ }
23
+
24
+ examples.each do |attr, cases|
25
+ describe "##{attr}" do
26
+ context "given a snippet with a #{attr}" do
27
+ let(:attrs) {
28
+ {snippet: {"#{attr.to_s.camelize(:lower)}" => cases[:input] || cases[:with]}}}
29
+ it { expect(comment.send(attr)).to eq cases[:with] }
30
+ end
31
+
32
+ context "given a snippet without a #{attr}" do
33
+ let(:attrs) { {snippet: {}} }
34
+ it { expect(comment.send(attr)).to eq cases[:without] }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/comment_thread'
3
+
4
+ describe Yt::CommentThread do
5
+ subject(:comment_thread) { Yt::CommentThread.new attrs }
6
+
7
+ describe '#snippet' do
8
+ context 'given fetching a comment thread returns a snippet' do
9
+ let(:attrs) { {snippet: {"videoId" => "12345"}} }
10
+ it { expect(comment_thread.snippet).to be_a Yt::Snippet }
11
+ end
12
+ end
13
+
14
+ describe '#video_id' do
15
+ context 'given a snippet with a video id' do
16
+ let(:attrs) { {snippet: {"videoId"=>"12345"}} }
17
+ it { expect(comment_thread.video_id).to eq '12345' }
18
+ end
19
+
20
+ context 'given a snippet without a video id' do
21
+ let(:attrs) { {snippet: {}} }
22
+ it { expect(comment_thread.video_id).to be_nil }
23
+ end
24
+ end
25
+
26
+ describe '#top_level_comment' do
27
+ context 'given a snippet with a top level comment' do
28
+ let(:attrs) { {snippet: {"topLevelComment"=> {}}} }
29
+ it { expect(comment_thread.top_level_comment).to be_a Yt::Comment }
30
+ end
31
+ end
32
+
33
+ describe 'attributes from #top_level_comment delegations' do
34
+ context 'with values' do
35
+ let(:attrs) { {snippet: {"topLevelComment"=> {"id" => "xyz123", "snippet" => {
36
+ "textDisplay" => "funny video!",
37
+ "authorDisplayName" => "fullscreen",
38
+ "likeCount" => 99,
39
+ "updatedAt" => "2016-03-22T12:56:56.3Z"}}}} }
40
+
41
+ it { expect(comment_thread.top_level_comment.id).to eq 'xyz123' }
42
+ it { expect(comment_thread.text_display).to eq 'funny video!' }
43
+ it { expect(comment_thread.author_display_name).to eq 'fullscreen' }
44
+ it { expect(comment_thread.like_count).to eq 99 }
45
+ it { expect(comment_thread.updated_at).to eq Time.parse('2016-03-22T12:56:56.3Z') }
46
+ end
47
+
48
+ context 'without values' do
49
+ let(:attrs) { {snippet: {"topLevelComment"=> {"snippet" => {}}}} }
50
+
51
+ it { expect(comment_thread.text_display).to be_nil }
52
+ it { expect(comment_thread.author_display_name).to be_nil }
53
+ it { expect(comment_thread.like_count).to be_nil }
54
+ it { expect(comment_thread.updated_at).to be_nil }
55
+ end
56
+ end
57
+
58
+ describe '#total_reply_count' do
59
+ context 'given a snippet with a total reply count' do
60
+ let(:attrs) { {snippet: {"totalReplyCount"=>1}} }
61
+ it { expect(comment_thread.total_reply_count).to eq 1 }
62
+ end
63
+
64
+ context 'given a snippet without a total reply count' do
65
+ let(:attrs) { {snippet: {}} }
66
+ it { expect(comment_thread.total_reply_count).to be_nil }
67
+ end
68
+ end
69
+
70
+ describe '#can_reply?' do
71
+ context 'given a snippet with canReply set' do
72
+ let(:attrs) { {snippet: {"canReply"=>true}} }
73
+ it { expect(comment_thread.can_reply?).to be true }
74
+ end
75
+
76
+ context 'given a snippet without canReply set' do
77
+ let(:attrs) { {snippet: {}} }
78
+ it { expect(comment_thread.can_reply?).to be false }
79
+ end
80
+ end
81
+
82
+ describe '#is_public?' do
83
+ context 'given a snippet with isPublic set' do
84
+ let(:attrs) { {snippet: {"isPublic"=>true}} }
85
+ it { expect(comment_thread).to be_public }
86
+ end
87
+
88
+ context 'given a snippet without isPublic set' do
89
+ let(:attrs) { {snippet: {}} }
90
+ it { expect(comment_thread).to_not be_public }
91
+ end
92
+ end
93
+ end
@@ -42,4 +42,11 @@ describe Yt::ContentDetail do
42
42
  it { expect(content_detail.duration).to eq 51 }
43
43
  end
44
44
  end
45
+
46
+ describe '#length' do
47
+ context 'returns the duration in HH:MM:SS' do
48
+ let(:data) { {"duration"=>"PT1H18M52S"} }
49
+ it { expect(content_detail.length).to eq '01:18:52' }
50
+ end
51
+ end
45
52
  end
@@ -156,8 +156,8 @@ describe Yt::Reference do
156
156
 
157
157
  describe '#video_id' do
158
158
  context 'given fetching a reference returns an videoId' do
159
- let(:data) { {"videoId"=>"MESycYJytkU"} }
160
- it { expect(reference.video_id).to eq 'MESycYJytkU' }
159
+ let(:data) { {"videoId"=>"9bZkp7q19f0"} }
160
+ it { expect(reference.video_id).to eq '9bZkp7q19f0' }
161
161
  end
162
162
  end
163
163
 
@@ -178,6 +178,27 @@ describe Yt::Request do
178
178
  it { expect{request.run}.not_to fail }
179
179
  end
180
180
  end
181
+
182
+ # NOTE: This test is just a reflection of YouTube irrational behavior of
183
+ # being unavailable once in a while, and therefore causing Net::HTTP to
184
+ # fail, although retrying after some seconds works.
185
+ context 'a SocketError', ruby21: true do
186
+ let(:http_error) { SocketError.new }
187
+
188
+ context 'every time' do
189
+ before { expect(Net::HTTP).to receive(:start).at_least(:once).and_raise http_error }
190
+
191
+ it { expect{request.run}.to fail }
192
+ end
193
+
194
+ context 'but works the second time' do
195
+ before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
196
+ before { allow(retry_response).to receive(:body) }
197
+ let(:retry_response) { Net::HTTPOK.new nil, nil, nil }
198
+
199
+ it { expect{request.run}.not_to fail }
200
+ end
201
+ end
181
202
  end
182
203
  end
183
204
  end
@@ -4,21 +4,6 @@ require 'yt/models/resource'
4
4
  describe Yt::Resource do
5
5
  subject(:resource) { Yt::Resource.new attrs }
6
6
 
7
- context 'given a resource initialized with a URL (containing an ID)' do
8
- let(:attrs) { {url: 'youtu.be/MESycYJytkU'} }
9
-
10
- it { expect(resource.id).to eq 'MESycYJytkU' }
11
- it { expect(resource.kind).to eq 'video' }
12
- it { expect(resource.username).to be_nil }
13
- end
14
-
15
- context 'given a resource initialized with a URL (containing a username)' do
16
- let(:attrs) { {url: 'youtube.com/fullscreen'} }
17
-
18
- it { expect(resource.kind).to eq 'channel' }
19
- it { expect(resource.username).to eq 'fullscreen' }
20
- end
21
-
22
7
  describe '#public?' do
23
8
  context 'given fetching a status returns privacyStatus "public"' do
24
9
  let(:attrs) { {status: {"privacyStatus"=>"public"}} }
@@ -649,7 +649,7 @@ describe Yt::Video do
649
649
  end
650
650
 
651
651
  describe '#update' do
652
- let(:attrs) { {id: 'MESycYJytkU', snippet: {'title'=>'old'}} }
652
+ let(:attrs) { {id: '9bZkp7q19f0', snippet: {'title'=>'old'}} }
653
653
  before { expect(video).to receive(:do_update).and_yield 'snippet'=>{'title'=>'new'} }
654
654
 
655
655
  it { expect(video.update title: 'new').to be true }
@@ -27,10 +27,7 @@ describe Yt::Account, :device_app do
27
27
  expect(uploads).not_to be_empty
28
28
  end
29
29
 
30
- specify 'includes private playlists (such as Watch Later or History)' do
31
- watch_later = related_playlists.select{|p| p.title == 'Watch Later'}
32
- expect(watch_later).not_to be_empty
33
-
30
+ specify 'includes private playlists (such as History)' do
34
31
  history = related_playlists.select{|p| p.title == 'History'}
35
32
  expect(history).not_to be_empty
36
33
  end
@@ -97,6 +94,21 @@ describe Yt::Account, :device_app do
97
94
  end
98
95
  end
99
96
 
97
+ describe '.video_groups' do
98
+ let(:video_group) { $account.video_groups.first }
99
+
100
+ specify 'returns the first video-group created by the account' do
101
+ expect(video_group).to be_a Yt::VideoGroup
102
+ expect(video_group.title).to be_a String
103
+ expect(video_group.item_count).to be_an Integer
104
+ expect(video_group.published_at).to be_a Time
105
+ end
106
+
107
+ specify 'allows to run reports against each video-group' do
108
+ expect(video_group.views).to be
109
+ end
110
+ end
111
+
100
112
  describe '.upload_video' do
101
113
  let(:video_params) { {title: 'Test Yt upload', privacy_status: 'private', category_id: 17} }
102
114
  let(:video) { $account.upload_video path_or_url, video_params }
@@ -41,7 +41,7 @@ describe Yt::Account, :device_app do
41
41
  end
42
42
 
43
43
  context 'that is invalid' do
44
- let(:authorization_code) { '--not-a-valid-authorization-code--' }
44
+ let(:authorization_code) { rand(36**20).to_s(36) }
45
45
  it { expect{account.authentication}.to raise_error Yt::Errors::Unauthorized }
46
46
  end
47
47
  end
@@ -104,18 +104,6 @@ describe Yt::Account, :device_app do
104
104
 
105
105
  it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
106
106
  end
107
-
108
- context 'and no device token' do
109
- it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
110
- end
111
-
112
- # NOTE: This test is commented out because of YouTube irrational behavior
113
- # of using to return "MissingAuth" when passing a wrong device code, and
114
- # now randomly returning `{"error"=>"internal_failure"}` instead.
115
- # context 'and an invalid device code' do
116
- # before { attrs[:device_code] = '--not-a-valid-device-code--' }
117
- # it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
118
- # end
119
107
  end
120
108
 
121
109
  context 'given no token or code' do