yt 0.32.6 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -4
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +22 -32
  5. data/YOUTUBE_IT.md +4 -4
  6. data/lib/yt.rb +0 -1
  7. data/lib/yt/associations/has_reports.rb +9 -14
  8. data/lib/yt/collections/reports.rb +5 -7
  9. data/lib/yt/models/resource.rb +69 -3
  10. data/lib/yt/models/url.rb +2 -60
  11. data/lib/yt/request.rb +6 -2
  12. data/lib/yt/version.rb +1 -1
  13. data/yt.gemspec +5 -2
  14. metadata +31 -169
  15. data/spec/collections/claims_spec.rb +0 -62
  16. data/spec/collections/comment_threads_spec.rb +0 -46
  17. data/spec/collections/playlist_items_spec.rb +0 -44
  18. data/spec/collections/playlists_spec.rb +0 -27
  19. data/spec/collections/policies_spec.rb +0 -30
  20. data/spec/collections/references_spec.rb +0 -30
  21. data/spec/collections/reports_spec.rb +0 -30
  22. data/spec/collections/subscriptions_spec.rb +0 -25
  23. data/spec/collections/videos_spec.rb +0 -43
  24. data/spec/constants/geography_spec.rb +0 -16
  25. data/spec/errors/forbidden_spec.rb +0 -10
  26. data/spec/errors/missing_auth_spec.rb +0 -24
  27. data/spec/errors/no_items_spec.rb +0 -10
  28. data/spec/errors/request_error_spec.rb +0 -44
  29. data/spec/errors/server_error_spec.rb +0 -10
  30. data/spec/errors/unauthorized_spec.rb +0 -10
  31. data/spec/models/account_spec.rb +0 -138
  32. data/spec/models/annotation_spec.rb +0 -180
  33. data/spec/models/asset_spec.rb +0 -32
  34. data/spec/models/channel_spec.rb +0 -127
  35. data/spec/models/claim_event_spec.rb +0 -62
  36. data/spec/models/claim_history_spec.rb +0 -27
  37. data/spec/models/claim_spec.rb +0 -223
  38. data/spec/models/comment_spec.rb +0 -40
  39. data/spec/models/comment_thread_spec.rb +0 -93
  40. data/spec/models/configuration_spec.rb +0 -44
  41. data/spec/models/content_detail_spec.rb +0 -52
  42. data/spec/models/content_owner_detail_spec.rb +0 -6
  43. data/spec/models/file_detail_spec.rb +0 -13
  44. data/spec/models/live_streaming_detail_spec.rb +0 -6
  45. data/spec/models/ownership_spec.rb +0 -59
  46. data/spec/models/player_spec.rb +0 -13
  47. data/spec/models/playlist_item_spec.rb +0 -120
  48. data/spec/models/playlist_spec.rb +0 -138
  49. data/spec/models/policy_rule_spec.rb +0 -63
  50. data/spec/models/policy_spec.rb +0 -41
  51. data/spec/models/rating_spec.rb +0 -12
  52. data/spec/models/reference_spec.rb +0 -249
  53. data/spec/models/request_spec.rb +0 -204
  54. data/spec/models/resource_spec.rb +0 -42
  55. data/spec/models/right_owner_spec.rb +0 -71
  56. data/spec/models/snippet_spec.rb +0 -13
  57. data/spec/models/statistics_set_spec.rb +0 -13
  58. data/spec/models/status_spec.rb +0 -13
  59. data/spec/models/subscription_spec.rb +0 -30
  60. data/spec/models/url_spec.rb +0 -78
  61. data/spec/models/video_category_spec.rb +0 -21
  62. data/spec/models/video_spec.rb +0 -669
  63. data/spec/requests/as_account/account_spec.rb +0 -143
  64. data/spec/requests/as_account/authentications_spec.rb +0 -127
  65. data/spec/requests/as_account/channel_spec.rb +0 -246
  66. data/spec/requests/as_account/channels_spec.rb +0 -18
  67. data/spec/requests/as_account/playlist_item_spec.rb +0 -55
  68. data/spec/requests/as_account/playlist_spec.rb +0 -218
  69. data/spec/requests/as_account/thumbnail.jpg +0 -0
  70. data/spec/requests/as_account/video.mp4 +0 -0
  71. data/spec/requests/as_account/video_spec.rb +0 -408
  72. data/spec/requests/as_content_owner/account_spec.rb +0 -29
  73. data/spec/requests/as_content_owner/advertising_options_set_spec.rb +0 -15
  74. data/spec/requests/as_content_owner/asset_spec.rb +0 -31
  75. data/spec/requests/as_content_owner/bulk_report_job_spec.rb +0 -19
  76. data/spec/requests/as_content_owner/channel_spec.rb +0 -1836
  77. data/spec/requests/as_content_owner/claim_history_spec.rb +0 -20
  78. data/spec/requests/as_content_owner/claim_spec.rb +0 -17
  79. data/spec/requests/as_content_owner/content_owner_spec.rb +0 -370
  80. data/spec/requests/as_content_owner/match_policy_spec.rb +0 -17
  81. data/spec/requests/as_content_owner/ownership_spec.rb +0 -25
  82. data/spec/requests/as_content_owner/playlist_spec.rb +0 -767
  83. data/spec/requests/as_content_owner/video_group_spec.rb +0 -112
  84. data/spec/requests/as_content_owner/video_spec.rb +0 -1223
  85. data/spec/requests/as_server_app/channel_spec.rb +0 -54
  86. data/spec/requests/as_server_app/comment_spec.rb +0 -22
  87. data/spec/requests/as_server_app/comment_thread_spec.rb +0 -27
  88. data/spec/requests/as_server_app/comment_threads_spec.rb +0 -41
  89. data/spec/requests/as_server_app/playlist_item_spec.rb +0 -30
  90. data/spec/requests/as_server_app/playlist_spec.rb +0 -33
  91. data/spec/requests/as_server_app/url_spec.rb +0 -94
  92. data/spec/requests/as_server_app/video_spec.rb +0 -60
  93. data/spec/requests/as_server_app/videos_spec.rb +0 -40
  94. data/spec/requests/unauthenticated/video_spec.rb +0 -14
  95. data/spec/spec_helper.rb +0 -20
  96. data/spec/support/fail_matcher.rb +0 -15
  97. data/spec/support/global_hooks.rb +0 -48
@@ -1,18 +0,0 @@
1
- # encoding: UTF-8
2
- require 'spec_helper'
3
- require 'yt/collections/channels'
4
-
5
- describe Yt::Collections::Channels, :device_app do
6
- subject(:channels) { Yt::Collections::Channels.new auth: $account }
7
-
8
- context 'with a list of parts' do
9
- let(:part) { 'statistics' }
10
- let(:channel) { channels.where(part: part, id: 'UCxO1tY8h1AhOz0T4ENwmpow').first }
11
-
12
- specify 'load ONLY the specified parts of the channels' do
13
- expect(channel.instance_variable_defined? :@snippet).to be false
14
- expect(channel.instance_variable_defined? :@status).to be false
15
- expect(channel.instance_variable_defined? :@statistics_set).to be true
16
- end
17
- end
18
- end
@@ -1,55 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/playlist_item'
3
-
4
- describe Yt::PlaylistItem, :device_app do
5
- subject(:item) { Yt::PlaylistItem.new id: id, auth: $account }
6
-
7
- context 'given an existing playlist item' do
8
- let(:id) { 'UExiai1JRGU2Zzh2c0FQT0RFci1xRUZjRERvWHhqRzhEVC41MjE1MkI0OTQ2QzJGNzNG' }
9
-
10
- it 'returns valid metadata' do
11
- expect(item.title).to be_a String
12
- expect(item.description).to be_a String
13
- expect(item.thumbnail_url).to be_a String
14
- expect(item.published_at).to be_a Time
15
- expect(item.channel_id).to be_a String
16
- expect(item.channel_title).to be_a String
17
- expect(item.playlist_id).to be_a String
18
- expect(item.position).to be_an Integer
19
- expect(item.video_id).to be_a String
20
- expect(item.video).to be_a Yt::Video
21
- expect(item.privacy_status).to be_a String
22
- end
23
- end
24
-
25
- context 'given an unknown playlist item' do
26
- let(:id) { 'not-a-playlist-item-id' }
27
-
28
- it { expect{item.snippet}.to raise_error Yt::Errors::RequestError }
29
- end
30
-
31
- context 'given one of my own playlist items that I want to update', rate_limited: true do
32
- before(:all) do
33
- @my_playlist = $account.create_playlist title: "Yt Test Update Playlist Item #{rand}"
34
- @my_playlist.add_video '9bZkp7q19f0'
35
- @my_playlist_item = @my_playlist.add_video '9bZkp7q19f0'
36
- end
37
- after(:all) { @my_playlist.delete }
38
-
39
- let(:id) { @my_playlist_item.id }
40
- let!(:old_title) { @my_playlist_item.title }
41
- let!(:old_privacy_status) { @my_playlist_item.privacy_status }
42
- let(:update) { @my_playlist_item.update attrs }
43
-
44
- context 'given I update the position' do
45
- let(:attrs) { {position: 0} }
46
-
47
- specify 'only updates the position' do
48
- expect(update).to be true
49
- expect(@my_playlist_item.position).to be 0
50
- expect(@my_playlist_item.title).to eq old_title
51
- expect(@my_playlist_item.privacy_status).to eq old_privacy_status
52
- end
53
- end
54
- end
55
- end
@@ -1,218 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'spec_helper'
4
- require 'yt/models/playlist'
5
-
6
- describe Yt::Playlist, :device_app do
7
- subject(:playlist) { Yt::Playlist.new id: id, auth: $account }
8
-
9
- context 'given an existing playlist' do
10
- let(:id) { 'PLpjK416fmKwQlQ0KvTWFmXZZa3d4IO2ro' } # from YouTube Creators
11
-
12
- it 'returns valid metadata' do
13
- expect(playlist.title).to be_a String
14
- expect(playlist.description).to be_a String
15
- expect(playlist.thumbnail_url).to be_a String
16
- expect(playlist.published_at).to be_a Time
17
- expect(playlist.tags).to be_an Array
18
- expect(playlist.channel_id).to be_a String
19
- expect(playlist.channel_title).to be_a String
20
- expect(playlist.privacy_status).to be_a String
21
- expect(playlist.item_count).to be_an Integer
22
- end
23
-
24
- describe '.playlist_items' do
25
- let(:item) { playlist.playlist_items.first }
26
-
27
- specify 'returns the playlist item with the complete snippet' do
28
- expect(item).to be_a Yt::PlaylistItem
29
- expect(item.snippet).to be_complete
30
- expect(item.position).not_to be_nil
31
- end
32
-
33
- specify 'does not eager-load the attributes of the item’s video' do
34
- expect(item.video.instance_variable_defined? :@snippet).to be false
35
- expect(item.video.instance_variable_defined? :@status).to be false
36
- expect(item.video.instance_variable_defined? :@statistics_set).to be false
37
- end
38
- end
39
-
40
- describe '.playlist_items.includes(:video)' do
41
- let(:item) { playlist.playlist_items.includes(:video).first }
42
-
43
- specify 'eager-loads the snippet, status and statistics of each video' do
44
- expect(item.video.instance_variable_defined? :@snippet).to be true
45
- expect(item.video.instance_variable_defined? :@status).to be true
46
- expect(item.video.instance_variable_defined? :@statistics_set).to be true
47
- end
48
- end
49
- end
50
-
51
- context 'given an unknown playlist' do
52
- let(:id) { 'not-a-playlist-id' }
53
-
54
- it { expect{playlist.snippet}.to raise_error Yt::Errors::NoItems }
55
- it { expect{playlist.status}.to raise_error Yt::Errors::NoItems }
56
- end
57
-
58
- context 'given someone else’s playlist' do
59
- let(:id) { 'PLpjK416fmKwQlQ0KvTWFmXZZa3d4IO2ro' } # from YouTube Creators
60
- let(:video_id) { '9bZkp7q19f0' }
61
-
62
- it { expect{playlist.delete}.to fail.with 'playlistForbidden' }
63
- it { expect{playlist.update}.to fail.with 'playlistForbidden' }
64
- it { expect{playlist.add_video! video_id}.to raise_error Yt::Errors::RequestError }
65
- it { expect{playlist.delete_playlist_items}.to raise_error Yt::Errors::RequestError }
66
- end
67
-
68
- context 'given one of my own playlists that I want to delete', rate_limited: true do
69
- before(:all) { @my_playlist = $account.create_playlist title: "Yt Test Delete Playlist #{rand}" }
70
- let(:id) { @my_playlist.id }
71
-
72
- it { expect(playlist.delete).to be true }
73
- end
74
-
75
- context 'given one of my own playlists that I want to update', rate_limited: true do
76
- before(:all) { @my_playlist = $account.create_playlist title: "Yt Test Update Playlist #{rand}" }
77
- after(:all) { @my_playlist.delete }
78
- let(:id) { @my_playlist.id }
79
- let!(:old_title) { @my_playlist.title }
80
- let!(:old_privacy_status) { @my_playlist.privacy_status }
81
- let(:update) { @my_playlist.update attrs }
82
-
83
- context 'given I update the title' do
84
- # NOTE: The use of UTF-8 characters is to test that we can pass up to
85
- # 50 characters, independently of their representation
86
- let(:attrs) { {title: "Yt Example Update Playlist #{rand} - ®•♡❥❦❧☙"} }
87
-
88
- specify 'only updates the title' do
89
- expect(update).to be true
90
- expect(@my_playlist.title).not_to eq old_title
91
- expect(@my_playlist.privacy_status).to eq old_privacy_status
92
- end
93
- end
94
-
95
- context 'given I update the description' do
96
- let!(:old_description) { @my_playlist.description }
97
- let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
98
-
99
- specify 'only updates the description' do
100
- expect(update).to be true
101
- expect(@my_playlist.description).not_to eq old_description
102
- expect(@my_playlist.title).to eq old_title
103
- expect(@my_playlist.privacy_status).to eq old_privacy_status
104
- end
105
- end
106
-
107
- context 'given I update the tags' do
108
- let!(:old_tags) { @my_playlist.tags }
109
- let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
110
-
111
- specify 'only updates the tag' do
112
- expect(update).to be true
113
- expect(@my_playlist.tags).not_to eq old_tags
114
- expect(@my_playlist.title).to eq old_title
115
- expect(@my_playlist.privacy_status).to eq old_privacy_status
116
- end
117
- end
118
-
119
- context 'given I update title, description and/or tags using angle brackets' do
120
- let(:attrs) { {title: "Yt Test < >", description: '< >', tags: ['<tag>']} }
121
-
122
- specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
123
- expect(update).to be true
124
- expect(playlist.title).to eq 'Yt Test ‹ ›'
125
- expect(playlist.description).to eq '‹ ›'
126
- expect(playlist.tags).to eq ['‹tag›']
127
- end
128
- end
129
-
130
- context 'given I update the privacy status' do
131
- let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
132
-
133
- context 'passing the parameter in underscore syntax' do
134
- let(:attrs) { {privacy_status: new_privacy_status} }
135
-
136
- specify 'only updates the privacy status' do
137
- expect(update).to be true
138
- expect(@my_playlist.privacy_status).not_to eq old_privacy_status
139
- expect(@my_playlist.title).to eq old_title
140
- end
141
- end
142
-
143
- context 'passing the parameter in camel-case syntax' do
144
- let(:attrs) { {privacyStatus: new_privacy_status} }
145
-
146
- specify 'only updates the privacy status' do
147
- expect(update).to be true
148
- expect(@my_playlist.privacy_status).not_to eq old_privacy_status
149
- expect(@my_playlist.title).to eq old_title
150
- end
151
- end
152
- end
153
-
154
- context 'given an existing video' do
155
- let(:video_id) { '9bZkp7q19f0' } # Gangnam Style
156
-
157
- describe 'can be added' do
158
- it { expect(playlist.add_video video_id).to be_a Yt::PlaylistItem }
159
- it { expect{playlist.add_video video_id}.to change{playlist.playlist_items.count}.by(1) }
160
- it { expect(playlist.add_video! video_id).to be_a Yt::PlaylistItem }
161
- it { expect{playlist.add_video! video_id}.to change{playlist.playlist_items.count}.by(1) }
162
- it { expect(playlist.add_video(video_id, position: 0).position).to be 0 }
163
- end
164
-
165
- # NOTE: This test sounds redundant, but it’s actually a reflection of
166
- # another irrational behavior of YouTube API. In short, if you add a new
167
- # video to a playlist, the returned item does not have the "position"
168
- # information. You need an extra call to get it. When YouTube fixes this
169
- # behavior, this test (and related code) will go away.
170
- describe 'adding the video' do
171
- let(:item) { playlist.add_video video_id }
172
-
173
- specify 'returns an item without its position' do
174
- expect(item.snippet).not_to be_complete
175
- expect(item.position).not_to be_nil # after reloading
176
- end
177
- end
178
-
179
- describe 'can be removed' do
180
- before { playlist.add_video video_id }
181
-
182
- it { expect(playlist.delete_playlist_items.uniq).to eq [true] }
183
- it { expect{playlist.delete_playlist_items}.to change{playlist.playlist_items.count} }
184
- end
185
- end
186
-
187
- context 'given an unknown video' do
188
- let(:video_id) { 'not-a-video' }
189
-
190
- describe 'cannot be added' do
191
- it { expect(playlist.add_video video_id).to be_nil }
192
- it { expect{playlist.add_video video_id}.not_to change{playlist.playlist_items.count} }
193
- it { expect{playlist.add_video! video_id}.to fail.with 'videoNotFound' }
194
- end
195
- end
196
-
197
- context 'given one existing and one unknown video' do
198
- let(:video_ids) { ['9bZkp7q19f0', 'not-a-video'] }
199
-
200
- describe 'only one can be added' do
201
- it { expect(playlist.add_videos(video_ids).length).to eq 2 }
202
- it { expect{playlist.add_videos video_ids}.to change{playlist.playlist_items.count}.by(1) }
203
- it { expect{playlist.add_videos! video_ids}.to fail.with 'videoNotFound' }
204
- end
205
- end
206
- end
207
-
208
- context 'given one of my own playlists that I want to get reports for' do
209
- let(:id) { $account.channel.playlists.first.id }
210
-
211
- it 'returns valid reports for playlist-related metrics' do
212
- expect{playlist.views}.not_to raise_error
213
- expect{playlist.playlist_starts}.not_to raise_error
214
- expect{playlist.average_time_in_playlist}.not_to raise_error
215
- expect{playlist.views_per_playlist_start}.not_to raise_error
216
- end
217
- end
218
- end
@@ -1,408 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'spec_helper'
4
- require 'yt/models/video'
5
-
6
- describe Yt::Video, :device_app do
7
- subject(:video) { Yt::Video.new id: id, auth: $account }
8
-
9
- context 'given someone else’s video' do
10
- let(:id) { '9bZkp7q19f0' }
11
-
12
- it { expect(video.content_detail).to be_a Yt::ContentDetail }
13
-
14
- it 'returns valid metadata' do
15
- expect(video.title).to be_a String
16
- expect(video.description).to be_a String
17
- expect(video.thumbnail_url).to be_a String
18
- expect(video.published_at).to be_a Time
19
- expect(video.privacy_status).to be_a String
20
- expect(video.tags).to be_an Array
21
- expect(video.channel_id).to be_a String
22
- expect(video.channel_title).to be_a String
23
- expect(video.channel_url).to be_a String
24
- expect(video.category_id).to be_a String
25
- expect(video.live_broadcast_content).to be_a String
26
- expect(video.view_count).to be_an Integer
27
- expect(video.like_count).to be_an Integer
28
- expect(video.dislike_count).to be_an Integer
29
- expect(video.favorite_count).to be_an Integer
30
- expect(video.comment_count).to be_an Integer
31
- expect(video.duration).to be_an Integer
32
- expect(video.length).to be_a String
33
- expect(video.hd?).to be_in [true, false]
34
- expect(video.stereoscopic?).to be_in [true, false]
35
- expect(video.captioned?).to be_in [true, false]
36
- expect(video.licensed?).to be_in [true, false]
37
- expect(video.deleted?).to be_in [true, false]
38
- expect(video.failed?).to be_in [true, false]
39
- expect(video.processed?).to be_in [true, false]
40
- expect(video.rejected?).to be_in [true, false]
41
- expect(video.uploading?).to be_in [true, false]
42
- expect(video.uses_unsupported_codec?).to be_in [true, false]
43
- expect(video.has_failed_conversion?).to be_in [true, false]
44
- expect(video.empty?).to be_in [true, false]
45
- expect(video.invalid?).to be_in [true, false]
46
- expect(video.too_small?).to be_in [true, false]
47
- expect(video.aborted?).to be_in [true, false]
48
- expect(video.claimed?).to be_in [true, false]
49
- expect(video.infringes_copyright?).to be_in [true, false]
50
- expect(video.duplicate?).to be_in [true, false]
51
- expect(video.scheduled_at.class).to be_in [NilClass, Time]
52
- expect(video.scheduled?).to be_in [true, false]
53
- expect(video.too_long?).to be_in [true, false]
54
- expect(video.violates_terms_of_use?).to be_in [true, false]
55
- expect(video.inappropriate?).to be_in [true, false]
56
- expect(video.infringes_trademark?).to be_in [true, false]
57
- expect(video.belongs_to_closed_account?).to be_in [true, false]
58
- expect(video.belongs_to_suspended_account?).to be_in [true, false]
59
- expect(video.licensed_as_creative_commons?).to be_in [true, false]
60
- expect(video.licensed_as_standard_youtube?).to be_in [true, false]
61
- expect(video.has_public_stats_viewable?).to be_in [true, false]
62
- expect(video.embeddable?).to be_in [true, false]
63
- expect(video.actual_start_time).to be_nil
64
- expect(video.actual_end_time).to be_nil
65
- expect(video.scheduled_start_time).to be_nil
66
- expect(video.scheduled_end_time).to be_nil
67
- expect(video.concurrent_viewers).to be_nil
68
- expect(video.embed_html).to be_a String
69
- expect(video.category_title).to be_a String
70
- end
71
-
72
- it { expect{video.update}.to fail }
73
- it { expect{video.delete}.to fail.with 'forbidden' }
74
-
75
- context 'that I like' do
76
- before { video.like }
77
- it { expect(video).to be_liked }
78
- it { expect(video.dislike).to be true }
79
- end
80
-
81
- context 'that I dislike' do
82
- before { video.dislike }
83
- it { expect(video).not_to be_liked }
84
- it { expect(video.like).to be true }
85
- end
86
-
87
- context 'that I am indifferent to' do
88
- before { video.unlike }
89
- it { expect(video).not_to be_liked }
90
- it { expect(video.like).to be true }
91
- end
92
- end
93
-
94
- context 'given someone else’s live video broadcast scheduled in the future' do
95
- let(:id) { 'PqzGI8gO_gk' }
96
-
97
- it 'returns valid live streaming details' do
98
- expect(video.actual_start_time).to be_nil
99
- expect(video.actual_end_time).to be_nil
100
- expect(video.scheduled_start_time).to be_a Time
101
- expect(video.scheduled_end_time).to be_nil
102
- end
103
- end
104
-
105
- context 'given someone else’s past live video broadcast' do
106
- let(:id) { 'COOM8_tOy6U' }
107
-
108
- it 'returns valid live streaming details' do
109
- expect(video.actual_start_time).to be_a Time
110
- expect(video.actual_end_time).to be_a Time
111
- expect(video.scheduled_start_time).to be_a Time
112
- expect(video.scheduled_end_time).to be_a Time
113
- expect(video.concurrent_viewers).to be_nil
114
- end
115
- end
116
-
117
- context 'given an unknown video' do
118
- let(:id) { 'not-a-video-id' }
119
-
120
- it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
121
- it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
122
- it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
123
- it { expect{video.status}.to raise_error Yt::Errors::NoItems }
124
- it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
125
- it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
126
- end
127
-
128
- context 'given one of my own videos that I want to delete' do
129
- before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
130
- let(:id) { @tmp_video.id }
131
-
132
- it { expect(video.delete).to be true }
133
- end
134
-
135
- context 'given one of my own videos that I want to update' do
136
- let(:id) { $account.videos.where(order: 'viewCount').first.id }
137
- let!(:old_title) { video.title }
138
- let!(:old_privacy_status) { video.privacy_status }
139
- let(:update) { video.update attrs }
140
-
141
- context 'given I update the title' do
142
- # NOTE: The use of UTF-8 characters is to test that we can pass up to
143
- # 50 characters, independently of their representation
144
- let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
145
-
146
- specify 'only updates the title' do
147
- expect(update).to be true
148
- expect(video.title).not_to eq old_title
149
- expect(video.privacy_status).to eq old_privacy_status
150
- end
151
- end
152
-
153
- context 'given I update the description' do
154
- let!(:old_description) { video.description }
155
- let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
156
-
157
- specify 'only updates the description' do
158
- expect(update).to be true
159
- expect(video.description).not_to eq old_description
160
- expect(video.title).to eq old_title
161
- expect(video.privacy_status).to eq old_privacy_status
162
- end
163
- end
164
-
165
- context 'given I update the tags' do
166
- let!(:old_tags) { video.tags }
167
- let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
168
-
169
- specify 'only updates the tag' do
170
- expect(update).to be true
171
- expect(video.tags).not_to eq old_tags
172
- expect(video.title).to eq old_title
173
- expect(video.privacy_status).to eq old_privacy_status
174
- end
175
- end
176
-
177
- context 'given I update the category ID' do
178
- let!(:old_category_id) { video.category_id }
179
- let!(:new_category_id) { old_category_id == '22' ? '23' : '22' }
180
-
181
- context 'passing the parameter in underscore syntax' do
182
- let(:attrs) { {category_id: new_category_id} }
183
-
184
- specify 'only updates the category ID' do
185
- expect(update).to be true
186
- expect(video.category_id).not_to eq old_category_id
187
- expect(video.title).to eq old_title
188
- expect(video.privacy_status).to eq old_privacy_status
189
- end
190
- end
191
-
192
- context 'passing the parameter in camel-case syntax' do
193
- let(:attrs) { {categoryId: new_category_id} }
194
-
195
- specify 'only updates the category ID' do
196
- expect(update).to be true
197
- expect(video.category_id).not_to eq old_category_id
198
- end
199
- end
200
- end
201
-
202
- context 'given I update title, description and/or tags using angle brackets' do
203
- let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
204
-
205
- specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
206
- expect(update).to be true
207
- expect(video.title).to eq 'Example Yt Test ‹ ›'
208
- expect(video.description).to eq '‹ ›'
209
- expect(video.tags).to eq ['‹tag›']
210
- end
211
- end
212
-
213
- # note: 'scheduled' videos cannot be set to 'unlisted'
214
- context 'given I update the privacy status' do
215
- before { video.update publish_at: nil if video.scheduled? }
216
- let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
217
-
218
- context 'passing the parameter in underscore syntax' do
219
- let(:attrs) { {privacy_status: new_privacy_status} }
220
-
221
- specify 'only updates the privacy status' do
222
- expect(update).to be true
223
- expect(video.privacy_status).not_to eq old_privacy_status
224
- expect(video.title).to eq old_title
225
- end
226
- end
227
-
228
- context 'passing the parameter in camel-case syntax' do
229
- let(:attrs) { {privacyStatus: new_privacy_status} }
230
-
231
- specify 'only updates the privacy status' do
232
- expect(update).to be true
233
- expect(video.privacy_status).not_to eq old_privacy_status
234
- expect(video.title).to eq old_title
235
- end
236
- end
237
- end
238
-
239
- context 'given I update the embeddable status' do
240
- let!(:old_embeddable) { video.embeddable? }
241
- let!(:new_embeddable) { !old_embeddable }
242
-
243
- let(:attrs) { {embeddable: new_embeddable} }
244
-
245
- # @see https://developers.google.com/youtube/v3/docs/videos/update
246
- specify 'does update the embeddable status' do
247
- expect(update).to be true
248
- expect(video.embeddable?).to eq new_embeddable
249
- end
250
- end
251
-
252
- context 'given I update the public stats viewable setting' do
253
- let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
254
- let!(:new_public_stats_viewable) { !old_public_stats_viewable }
255
-
256
- context 'passing the parameter in underscore syntax' do
257
- let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
258
-
259
- specify 'only updates the public stats viewable setting' do
260
- expect(update).to be true
261
- expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
262
- expect(video.privacy_status).to eq old_privacy_status
263
- expect(video.title).to eq old_title
264
- end
265
- end
266
-
267
- context 'passing the parameter in camel-case syntax' do
268
- let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
269
-
270
- specify 'only updates the public stats viewable setting' do
271
- expect(update).to be true
272
- expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
273
- expect(video.privacy_status).to eq old_privacy_status
274
- expect(video.title).to eq old_title
275
- end
276
- end
277
- end
278
-
279
- it 'returns valid reports for video-related metrics', extended_permissions: true do
280
- # Some reports are only available to Content Owners.
281
- # See content owner test for more details about what the methods return.
282
- expect{video.views}.not_to raise_error
283
- expect{video.comments}.not_to raise_error
284
- expect{video.likes}.not_to raise_error
285
- expect{video.dislikes}.not_to raise_error
286
- expect{video.shares}.not_to raise_error
287
- expect{video.subscribers_gained}.not_to raise_error
288
- expect{video.subscribers_lost}.not_to raise_error
289
- expect{video.videos_added_to_playlists}.not_to raise_error
290
- expect{video.videos_removed_from_playlists}.not_to raise_error
291
- expect{video.estimated_minutes_watched}.not_to raise_error
292
- expect{video.average_view_duration}.not_to raise_error
293
- expect{video.average_view_percentage}.not_to raise_error
294
- expect{video.annotation_clicks}.not_to raise_error
295
- expect{video.annotation_click_through_rate}.not_to raise_error
296
- expect{video.annotation_close_rate}.not_to raise_error
297
- expect{video.card_impressions}.not_to raise_error
298
- expect{video.card_clicks}.not_to raise_error
299
- expect{video.card_click_rate}.not_to raise_error
300
- expect{video.card_teaser_impressions}.not_to raise_error
301
- expect{video.card_teaser_clicks}.not_to raise_error
302
- expect{video.card_teaser_click_rate}.not_to raise_error
303
- expect{video.viewer_percentage}.not_to raise_error
304
- expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
305
- expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
306
- expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
307
- expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
308
- expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
309
- end
310
- end
311
-
312
- # @note: This test is separated from the block above because, for some
313
- # undocumented reasons, if an existing video was private, then set to
314
- # unlisted, then set to private again, YouTube _sometimes_ raises a
315
- # 400 Error when trying to set the publishAt timestamp.
316
- # Therefore, just to test the updating of publishAt, we use a brand new
317
- # video (set to private), rather than reusing an existing one as above.
318
- context 'given one of my own *private* videos that I want to update' do
319
- before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
320
- let(:id) { @tmp_video.id }
321
- let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
322
- let!(:old_privacy_status) { 'private' }
323
- after { video.delete }
324
-
325
- let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
326
-
327
- context 'passing the parameter in underscore syntax' do
328
- let(:attrs) { {publish_at: new_scheduled_at} }
329
-
330
- specify 'only updates the timestamp to publish the video', flaky: true do
331
- expect(video.update attrs).to be true
332
- expect(video.privacy_status).to eq old_privacy_status
333
- expect(video.title).to eq old_title
334
- # NOTE: This is another irrational behavior of YouTube API. In short,
335
- # the response of Video#update *does not* include the publishAt value
336
- # even if it exists. You need to call Video#list again to get it.
337
- video = Yt::Video.new id: id, auth: $account
338
- expect(video.scheduled_at).to eq new_scheduled_at
339
- # Setting a private (scheduled) video to private has no effect:
340
- expect(video.update privacy_status: 'private').to be true
341
- video = Yt::Video.new id: id, auth: $account
342
- expect(video.scheduled_at).to eq new_scheduled_at
343
- # Setting a private (scheduled) video to unlisted/public removes publishAt:
344
- expect(video.update privacy_status: 'unlisted').to be true
345
- video = Yt::Video.new id: id, auth: $account
346
- expect(video.scheduled_at).to be_nil
347
- end
348
- end
349
-
350
- context 'passing the parameter in camel-case syntax' do
351
- let(:attrs) { {publishAt: new_scheduled_at} }
352
-
353
- specify 'only updates the timestamp to publish the video' do
354
- expect(video.update attrs).to be true
355
- video = Yt::Video.new id: id, auth: $account
356
- expect(video.scheduled_at).to eq new_scheduled_at
357
- expect(video.privacy_status).to eq old_privacy_status
358
- expect(video.title).to eq old_title
359
- end
360
- end
361
- end
362
-
363
- # @note: This should somehow test that the thumbnail *changes*. However,
364
- # YouTube does not change the URL of the thumbnail even though the content
365
- # changes. A full test would have to *download* the thumbnails before and
366
- # after, and compare the files. For now, not raising error is enough.
367
- # Eventually, change to `expect{update}.to change{video.thumbnail_url}`
368
- context 'given one of my own videos for which I want to upload a thumbnail' do
369
- let(:id) { $account.videos.where(order: 'viewCount').first.id }
370
- let(:update) { video.upload_thumbnail path_or_url }
371
-
372
- context 'given the path to a local JPG image file', extended_permissions: true do
373
- let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
374
-
375
- it { expect{update}.not_to raise_error }
376
- end
377
-
378
- context 'given the path to a remote PNG image file', extended_permissions: true do
379
- let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
380
-
381
- it { expect{update}.not_to raise_error }
382
- end
383
-
384
- context 'given an invalid URL' do
385
- let(:path_or_url) { 'this-is-not-a-url' }
386
-
387
- it { expect{update}.to raise_error Yt::Errors::RequestError }
388
- end
389
- end
390
-
391
- # @note: This test is separated from the block above because YouTube only
392
- # returns file details for *some videos*: "The fileDetails object will
393
- # only be returned if the processingDetails.fileAvailability property
394
- # has a value of 'available'." Therefore, just to test fileDetails, we use a
395
- # different video (I couldn't find any video marked as 'available').
396
- # @see https://developers.google.com/youtube/v3/docs/videos#fileDetails
397
- # @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
398
- context 'given one of my own videos' do
399
- let(:id) { $account.videos.first.id }
400
-
401
- it 'returns valid file details' do
402
- expect(video.file_name).to be_a String
403
- expect(video.file_size).to be_nil
404
- expect(video.file_type).to be_nil
405
- expect(video.container).to be_nil
406
- end
407
- end
408
- end