yt-andrewroth 0.25.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. data/.gitignore +27 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +9 -0
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +732 -0
  6. data/Gemfile +4 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +489 -0
  9. data/Rakefile +11 -0
  10. data/YOUTUBE_IT.md +835 -0
  11. data/bin/yt +30 -0
  12. data/gemfiles/Gemfile.activesupport-3.x +4 -0
  13. data/gemfiles/Gemfile.activesupport-4.x +4 -0
  14. data/lib/yt.rb +21 -0
  15. data/lib/yt/actions/base.rb +32 -0
  16. data/lib/yt/actions/delete.rb +19 -0
  17. data/lib/yt/actions/delete_all.rb +32 -0
  18. data/lib/yt/actions/insert.rb +42 -0
  19. data/lib/yt/actions/list.rb +139 -0
  20. data/lib/yt/actions/modify.rb +37 -0
  21. data/lib/yt/actions/patch.rb +19 -0
  22. data/lib/yt/actions/update.rb +19 -0
  23. data/lib/yt/associations/has_attribute.rb +55 -0
  24. data/lib/yt/associations/has_authentication.rb +214 -0
  25. data/lib/yt/associations/has_many.rb +22 -0
  26. data/lib/yt/associations/has_one.rb +22 -0
  27. data/lib/yt/associations/has_reports.rb +320 -0
  28. data/lib/yt/collections/advertising_options_sets.rb +34 -0
  29. data/lib/yt/collections/annotations.rb +62 -0
  30. data/lib/yt/collections/assets.rb +58 -0
  31. data/lib/yt/collections/authentications.rb +47 -0
  32. data/lib/yt/collections/base.rb +62 -0
  33. data/lib/yt/collections/channels.rb +31 -0
  34. data/lib/yt/collections/claim_histories.rb +34 -0
  35. data/lib/yt/collections/claims.rb +56 -0
  36. data/lib/yt/collections/content_details.rb +30 -0
  37. data/lib/yt/collections/content_owner_details.rb +34 -0
  38. data/lib/yt/collections/content_owners.rb +32 -0
  39. data/lib/yt/collections/device_flows.rb +23 -0
  40. data/lib/yt/collections/file_details.rb +30 -0
  41. data/lib/yt/collections/ids.rb +27 -0
  42. data/lib/yt/collections/live_streaming_details.rb +30 -0
  43. data/lib/yt/collections/ownerships.rb +34 -0
  44. data/lib/yt/collections/partnered_channels.rb +28 -0
  45. data/lib/yt/collections/players.rb +30 -0
  46. data/lib/yt/collections/playlist_items.rb +53 -0
  47. data/lib/yt/collections/playlists.rb +28 -0
  48. data/lib/yt/collections/policies.rb +28 -0
  49. data/lib/yt/collections/ratings.rb +23 -0
  50. data/lib/yt/collections/references.rb +46 -0
  51. data/lib/yt/collections/related_playlists.rb +43 -0
  52. data/lib/yt/collections/reports.rb +161 -0
  53. data/lib/yt/collections/resources.rb +57 -0
  54. data/lib/yt/collections/resumable_sessions.rb +51 -0
  55. data/lib/yt/collections/snippets.rb +27 -0
  56. data/lib/yt/collections/statistics_sets.rb +30 -0
  57. data/lib/yt/collections/statuses.rb +27 -0
  58. data/lib/yt/collections/subscribed_channels.rb +46 -0
  59. data/lib/yt/collections/subscribers.rb +33 -0
  60. data/lib/yt/collections/subscriptions.rb +50 -0
  61. data/lib/yt/collections/user_infos.rb +36 -0
  62. data/lib/yt/collections/video_categories.rb +35 -0
  63. data/lib/yt/collections/videos.rb +137 -0
  64. data/lib/yt/config.rb +54 -0
  65. data/lib/yt/errors/forbidden.rb +13 -0
  66. data/lib/yt/errors/missing_auth.rb +81 -0
  67. data/lib/yt/errors/no_items.rb +13 -0
  68. data/lib/yt/errors/request_error.rb +74 -0
  69. data/lib/yt/errors/server_error.rb +13 -0
  70. data/lib/yt/errors/unauthorized.rb +50 -0
  71. data/lib/yt/models/account.rb +216 -0
  72. data/lib/yt/models/advertising_options_set.rb +38 -0
  73. data/lib/yt/models/annotation.rb +132 -0
  74. data/lib/yt/models/asset.rb +111 -0
  75. data/lib/yt/models/asset_metadata.rb +38 -0
  76. data/lib/yt/models/asset_snippet.rb +46 -0
  77. data/lib/yt/models/authentication.rb +83 -0
  78. data/lib/yt/models/base.rb +32 -0
  79. data/lib/yt/models/channel.rb +302 -0
  80. data/lib/yt/models/claim.rb +156 -0
  81. data/lib/yt/models/claim_event.rb +67 -0
  82. data/lib/yt/models/claim_history.rb +29 -0
  83. data/lib/yt/models/configuration.rb +70 -0
  84. data/lib/yt/models/content_detail.rb +65 -0
  85. data/lib/yt/models/content_owner.rb +48 -0
  86. data/lib/yt/models/content_owner_detail.rb +18 -0
  87. data/lib/yt/models/description.rb +58 -0
  88. data/lib/yt/models/device_flow.rb +16 -0
  89. data/lib/yt/models/file_detail.rb +21 -0
  90. data/lib/yt/models/id.rb +9 -0
  91. data/lib/yt/models/iterator.rb +16 -0
  92. data/lib/yt/models/live_streaming_detail.rb +23 -0
  93. data/lib/yt/models/match_policy.rb +34 -0
  94. data/lib/yt/models/ownership.rb +75 -0
  95. data/lib/yt/models/player.rb +18 -0
  96. data/lib/yt/models/playlist.rb +218 -0
  97. data/lib/yt/models/playlist_item.rb +112 -0
  98. data/lib/yt/models/policy.rb +36 -0
  99. data/lib/yt/models/policy_rule.rb +124 -0
  100. data/lib/yt/models/rating.rb +37 -0
  101. data/lib/yt/models/reference.rb +172 -0
  102. data/lib/yt/models/resource.rb +136 -0
  103. data/lib/yt/models/resumable_session.rb +52 -0
  104. data/lib/yt/models/right_owner.rb +58 -0
  105. data/lib/yt/models/snippet.rb +50 -0
  106. data/lib/yt/models/statistics_set.rb +26 -0
  107. data/lib/yt/models/status.rb +32 -0
  108. data/lib/yt/models/subscription.rb +38 -0
  109. data/lib/yt/models/timestamp.rb +13 -0
  110. data/lib/yt/models/url.rb +90 -0
  111. data/lib/yt/models/user_info.rb +26 -0
  112. data/lib/yt/models/video.rb +630 -0
  113. data/lib/yt/models/video_category.rb +12 -0
  114. data/lib/yt/request.rb +278 -0
  115. data/lib/yt/version.rb +3 -0
  116. data/spec/collections/claims_spec.rb +30 -0
  117. data/spec/collections/playlist_items_spec.rb +44 -0
  118. data/spec/collections/playlists_spec.rb +27 -0
  119. data/spec/collections/policies_spec.rb +30 -0
  120. data/spec/collections/references_spec.rb +30 -0
  121. data/spec/collections/reports_spec.rb +30 -0
  122. data/spec/collections/subscriptions_spec.rb +25 -0
  123. data/spec/collections/videos_spec.rb +43 -0
  124. data/spec/errors/forbidden_spec.rb +10 -0
  125. data/spec/errors/missing_auth_spec.rb +24 -0
  126. data/spec/errors/no_items_spec.rb +10 -0
  127. data/spec/errors/request_error_spec.rb +44 -0
  128. data/spec/errors/server_error_spec.rb +10 -0
  129. data/spec/errors/unauthorized_spec.rb +10 -0
  130. data/spec/models/account_spec.rb +138 -0
  131. data/spec/models/annotation_spec.rb +180 -0
  132. data/spec/models/asset_spec.rb +20 -0
  133. data/spec/models/channel_spec.rb +127 -0
  134. data/spec/models/claim_event_spec.rb +62 -0
  135. data/spec/models/claim_history_spec.rb +27 -0
  136. data/spec/models/claim_spec.rb +211 -0
  137. data/spec/models/configuration_spec.rb +44 -0
  138. data/spec/models/content_detail_spec.rb +45 -0
  139. data/spec/models/content_owner_detail_spec.rb +6 -0
  140. data/spec/models/description_spec.rb +94 -0
  141. data/spec/models/file_detail_spec.rb +13 -0
  142. data/spec/models/live_streaming_detail_spec.rb +6 -0
  143. data/spec/models/ownership_spec.rb +59 -0
  144. data/spec/models/player_spec.rb +13 -0
  145. data/spec/models/playlist_item_spec.rb +120 -0
  146. data/spec/models/playlist_spec.rb +138 -0
  147. data/spec/models/policy_rule_spec.rb +63 -0
  148. data/spec/models/policy_spec.rb +41 -0
  149. data/spec/models/rating_spec.rb +12 -0
  150. data/spec/models/reference_spec.rb +249 -0
  151. data/spec/models/request_spec.rb +163 -0
  152. data/spec/models/resource_spec.rb +57 -0
  153. data/spec/models/right_owner_spec.rb +71 -0
  154. data/spec/models/snippet_spec.rb +13 -0
  155. data/spec/models/statistics_set_spec.rb +13 -0
  156. data/spec/models/status_spec.rb +13 -0
  157. data/spec/models/subscription_spec.rb +30 -0
  158. data/spec/models/url_spec.rb +78 -0
  159. data/spec/models/video_category_spec.rb +21 -0
  160. data/spec/models/video_spec.rb +669 -0
  161. data/spec/requests/as_account/account_spec.rb +125 -0
  162. data/spec/requests/as_account/authentications_spec.rb +139 -0
  163. data/spec/requests/as_account/channel_spec.rb +259 -0
  164. data/spec/requests/as_account/channels_spec.rb +18 -0
  165. data/spec/requests/as_account/playlist_item_spec.rb +56 -0
  166. data/spec/requests/as_account/playlist_spec.rb +244 -0
  167. data/spec/requests/as_account/resource_spec.rb +18 -0
  168. data/spec/requests/as_account/thumbnail.jpg +0 -0
  169. data/spec/requests/as_account/video.mp4 +0 -0
  170. data/spec/requests/as_account/video_spec.rb +408 -0
  171. data/spec/requests/as_content_owner/account_spec.rb +25 -0
  172. data/spec/requests/as_content_owner/advertising_options_set_spec.rb +15 -0
  173. data/spec/requests/as_content_owner/asset_spec.rb +20 -0
  174. data/spec/requests/as_content_owner/channel_spec.rb +1934 -0
  175. data/spec/requests/as_content_owner/claim_history_spec.rb +20 -0
  176. data/spec/requests/as_content_owner/content_owner_spec.rb +241 -0
  177. data/spec/requests/as_content_owner/match_policy_spec.rb +17 -0
  178. data/spec/requests/as_content_owner/ownership_spec.rb +25 -0
  179. data/spec/requests/as_content_owner/playlist_spec.rb +782 -0
  180. data/spec/requests/as_content_owner/video_spec.rb +1239 -0
  181. data/spec/requests/as_server_app/channel_spec.rb +74 -0
  182. data/spec/requests/as_server_app/playlist_item_spec.rb +30 -0
  183. data/spec/requests/as_server_app/playlist_spec.rb +53 -0
  184. data/spec/requests/as_server_app/video_spec.rb +58 -0
  185. data/spec/requests/as_server_app/videos_spec.rb +40 -0
  186. data/spec/requests/unauthenticated/video_spec.rb +22 -0
  187. data/spec/spec_helper.rb +20 -0
  188. data/spec/support/fail_matcher.rb +15 -0
  189. data/spec/support/global_hooks.rb +48 -0
  190. data/yt.gemspec +32 -0
  191. metadata +416 -0
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/claim'
3
+ require 'yt/models/claim_history'
4
+
5
+ describe Yt::ClaimHistory, :partner do
6
+ subject(:claim) { Yt::Claim.new id: asset_id, auth: $content_owner }
7
+
8
+ context 'given a claim previously administered by the authenticated Content Owner' do
9
+ let(:asset_id) { ENV['YT_TEST_CLAIM_ID'] }
10
+
11
+ describe 'the claim history can be obtained' do
12
+ it { expect(claim.claim_history.events.size > 0).to eq true }
13
+ end
14
+
15
+ describe 'the claim_create event' do
16
+ let(:claim_create_event) { claim.claim_history.events.find {|e| e.type == 'claim_create'} }
17
+ it { expect(claim_create_event.time).to be_a Time }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,241 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/content_owner'
3
+
4
+ describe Yt::ContentOwner, :partner do
5
+ describe '.partnered_channels' do
6
+ let(:partnered_channels) { $content_owner.partnered_channels }
7
+
8
+ specify '.first' do
9
+ expect(partnered_channels.first).to be_a Yt::Channel
10
+ end
11
+
12
+ specify '.size', :ruby2 do
13
+ expect(partnered_channels.size).to be > 0
14
+ end
15
+ end
16
+
17
+ describe 'claims' do
18
+ let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
19
+ let(:video_id) { ENV['YT_TEST_PARTNER_CLAIMABLE_VIDEO_ID'] }
20
+ let(:options) { {asset_id: asset_id, video_id: video_id, content_type: 'audiovisual'} }
21
+
22
+ context 'given an existing policy ID' do
23
+ let(:policy_id) { ENV['YT_TEST_PARTNER_POLICY_ID'] }
24
+ let(:params) { options.merge policy_id: policy_id }
25
+
26
+ specify 'can be added' do
27
+ begin
28
+ expect($content_owner.create_claim params).to be_a Yt::Claim
29
+ rescue Yt::Errors::RequestError => e
30
+ # @note: Every time this test runs, a claim is inserted for the same
31
+ # video and asset, but YouTube does not allow this, and responds with
32
+ # an error message like "Video is already claimed. Existing claims
33
+ # on this video: AbCdEFg1234".
34
+ # For the sake of testing, we delete the duplicate and try again.
35
+ raise unless e.reasons.include? 'alreadyClaimed'
36
+ id = e.kind['message'].match(/this video: (.*?)$/) {|re| re[1]}
37
+ Yt::Claim.new(id: id, auth: $content_owner).delete
38
+ expect($content_owner.create_claim params).to be_a Yt::Claim
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'given a new policy' do
44
+ let(:params) { options.merge policy: {rules: [action: :block]} }
45
+
46
+ specify 'can be added' do
47
+ begin
48
+ expect($content_owner.create_claim params).to be_a Yt::Claim
49
+ rescue Yt::Errors::RequestError => e
50
+ # @note: Every time this test runs, a claim is inserted for the same
51
+ # video and asset, but YouTube does not allow this, and responds with
52
+ # an error message like "Video is already claimed. Existing claims
53
+ # on this video: AbCdEFg1234".
54
+ # For the sake of testing, we delete the duplicate and try again.
55
+ raise unless e.reasons.include? 'alreadyClaimed'
56
+ id = e.kind['message'].match(/this video: (.*?)$/) {|re| re[1]}
57
+ Yt::Claim.new(id: id, auth: $content_owner).delete
58
+ expect($content_owner.create_claim params).to be_a Yt::Claim
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '.claims' do
65
+ describe 'given the content owner has claims' do
66
+ let(:claim) { $content_owner.claims.first }
67
+
68
+ it 'returns valid metadata' do
69
+ expect(claim.id).to be_a String
70
+ expect(claim.asset_id).to be_a String
71
+ expect(claim.video_id).to be_a String
72
+ expect(claim.status).to be_a String
73
+ expect(claim.content_type).to be_a String
74
+ expect(claim.created_at).to be_a Time
75
+ expect(claim).not_to be_third_party
76
+ end
77
+ end
78
+
79
+ describe '.where(id: claim_id)' do
80
+ let(:count) { $content_owner.claims.where(id: claim_id).count }
81
+
82
+ context 'given the ID of a claim administered by the content owner' do
83
+ let(:claim_id) { ENV['YT_TEST_PARTNER_CLAIM_ID'] }
84
+ it { expect(count).to be > 0 }
85
+ end
86
+
87
+ context 'given an unknown claim ID' do
88
+ let(:claim_id) { '--not-a-matching-claim-id--' }
89
+ it { expect(count).to be_zero }
90
+ end
91
+ end
92
+
93
+ describe '.where(asset_id: asset_id)' do
94
+ let(:count) { $content_owner.claims.where(asset_id: asset_id).count }
95
+
96
+ context 'given the asset ID of a claim administered by the content owner' do
97
+ let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_WITH_CLAIM_ID'] }
98
+ it { expect(count).to be > 0 }
99
+ end
100
+
101
+ context 'given an unknown asset ID' do
102
+ let(:asset_id) { 'A123456789012345' }
103
+ it { expect(count).to be_zero }
104
+ end
105
+ end
106
+
107
+ describe '.where(video_id: video_id)' do
108
+ let(:count) { $content_owner.claims.where(video_id: video_id).count }
109
+
110
+ context 'given the video ID of a claim administered by the content owner' do
111
+ let(:video_id) { ENV['YT_TEST_PARTNER_VIDEO_ID'] }
112
+ it { expect(count).to be > 0 }
113
+ end
114
+ end
115
+
116
+ describe '.where(q: query)' do
117
+ let(:first) { $content_owner.claims.where(params).first }
118
+
119
+ context 'given a query matching the title of a video of a claim administered by the content owner' do
120
+ let(:query) { ENV['YT_TEST_PARTNER_Q'] }
121
+
122
+ context 'given no optional filters' do
123
+ let(:params) { {q: query} }
124
+ it { expect(first).to be }
125
+ end
126
+
127
+ context 'given an optional filter that does not include that video' do
128
+ let(:remote_date) { '2008-01-01T00:00:00.000Z' }
129
+
130
+ describe 'applies the filter if the filter name is under_scored' do
131
+ let(:params) { {q: query, created_before: remote_date} }
132
+ it { expect(first).not_to be }
133
+ end
134
+
135
+ describe 'applies the filter if the filter name is camelCased' do
136
+ let(:params) { {q: query, createdBefore: remote_date} }
137
+ it { expect(first).not_to be }
138
+ end
139
+ end
140
+ end
141
+
142
+ context 'given an unknown video ID' do
143
+ let(:params) { {q: '--not-a-matching-query--'} }
144
+ it { expect(first).not_to be }
145
+ end
146
+ end
147
+ end
148
+
149
+ describe 'references' do
150
+ let(:claim_id) { ENV['YT_TEST_PARTNER_REFERENCE_CLAIM_ID'] }
151
+ let(:content_type) { ENV['YT_TEST_PARTNER_REFERENCE_CONTENT_TYPE'] }
152
+ let(:params) { {claim_id: claim_id, content_type: content_type} }
153
+
154
+ specify 'can be added' do
155
+ begin
156
+ expect($content_owner.create_reference params).to be_a Yt::Reference
157
+ rescue Yt::Errors::RequestError => e
158
+ # @note: Every time this test runs, a reference is inserted for the
159
+ # same claim, but YouTube does not allow this, and responds with an
160
+ # error message like "You attempted to create a reference using the
161
+ # content of a previously claimed video, but such a reference already
162
+ # exists. The ID of the duplicate reference is xhpACYclOdc."
163
+ # For the sake of testing, we delete the duplicate and try again.
164
+ # @note: Deleting a reference does not work if the reference status is
165
+ # "checking" or "pending" and it can take up to 4 minutes for a new
166
+ # reference to be checked. The +sleep+ statement takes care of this
167
+ # case in the only way allowed by YouTube: sadly waiting.
168
+ raise unless e.reasons.include? 'referenceAlreadyExists'
169
+ id = e.kind['message'].match(/reference is (.*?)\.$/) {|re| re[1]}
170
+ sleep 15 until Yt::Reference.new(id: id, auth: $content_owner).delete
171
+ expect($content_owner.create_reference params).to be_a Yt::Reference
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '.references' do
177
+ describe '.where(id: reference_id)' do
178
+ let(:reference) { $content_owner.references.where(id: reference_id).first }
179
+
180
+ context 'given the ID of a reference administered by the content owner' do
181
+ let(:reference_id) { ENV['YT_TEST_PARTNER_REFERENCE_ID'] }
182
+
183
+ it 'returns valid metadata' do
184
+ expect(reference.id).to be_a String
185
+ expect(reference.asset_id).to be_a String
186
+ expect(reference.length).to be_a Float
187
+ expect(reference.video_id).to be_a String
188
+ expect(reference.claim_id).to be_a String
189
+ expect(reference.audioswap_enabled?).to be_in [true, false]
190
+ expect(reference.ignore_fp_match?).to be_in [true, false]
191
+ expect(reference.urgent?).to be_in [true, false]
192
+ expect(reference.status).to be_a String
193
+ expect(reference.content_type).to be_a String
194
+ end
195
+ end
196
+
197
+ context 'given an unknown reference ID' do
198
+ let(:reference_id) { '--not-a-matching-reference-id--' }
199
+ it { expect(reference).not_to be }
200
+ end
201
+ end
202
+ end
203
+
204
+ describe '.policies' do
205
+ describe 'given the content owner has policies' do
206
+ let(:policy) { $content_owner.policies.first }
207
+ let(:rule) { policy.rules.first }
208
+
209
+ it 'returns valid metadata' do
210
+ expect(policy.id).to be_a String
211
+ expect(policy.name).to be_a String
212
+ expect(policy.updated_at).to be_a Time
213
+ expect(rule.action).to be_a String
214
+ expect(rule.included_territories).to be_an Array
215
+ expect(rule.excluded_territories).to be_an Array
216
+ expect(rule.match_duration).to be_an Array
217
+ expect(rule.match_percent).to be_an Array
218
+ expect(rule.reference_duration).to be_an Array
219
+ expect(rule.reference_percent).to be_an Array
220
+ end
221
+ end
222
+
223
+ describe '.where(id: policy_id) given an unknown policy ID' do
224
+ let(:policy) { $content_owner.policies.where(id: policy_id).first }
225
+ let(:policy_id) { '--not-a-matching-reference-id--' }
226
+ it { expect(policy).not_to be }
227
+ end
228
+ end
229
+
230
+ # @note: The following test works, but YouTube API endpoint to mark
231
+ # an asset as 'invalid' (soft-delete) does not work, and apparently
232
+ # there is no way to update the status of a asset.
233
+ # Therefore, the test is commented out, otherwise a new asset would
234
+ # be created every time the test is run.
235
+ # describe 'assets can be added' do
236
+ # let(:params) { {type: 'web', metadata_mine: {title: "Test Asset Title"}} }
237
+ # before { @asset = $content_owner.create_asset params }
238
+ # after { @asset.delete } # This does not seem to work
239
+ # it { expect(@asset).to be_a Yt::Asset }
240
+ # end
241
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/match_policy'
3
+
4
+ describe Yt::MatchPolicy, :partner do
5
+ subject(:match_policy) { Yt::MatchPolicy.new asset_id: asset_id, auth: $content_owner }
6
+
7
+ context 'given an asset managed by the authenticated Content Owner' do
8
+ before { Yt::Ownership.new(asset_id: asset_id, auth: $content_owner).obtain! }
9
+ let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
10
+
11
+ describe 'the asset match policy can be updated' do
12
+ let(:policy_id) { ENV['YT_TEST_PARTNER_POLICY_ID'] }
13
+
14
+ it { expect(match_policy.update policy_id: policy_id).to be true }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/ownership'
3
+
4
+ describe Yt::Ownership, :partner do
5
+ subject(:ownership) { Yt::Ownership.new asset_id: asset_id, auth: $content_owner }
6
+
7
+ context 'given an asset managed by the authenticated Content Owner' do
8
+ let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
9
+
10
+ describe 'the ownership can be updated' do
11
+ let(:general_owner) { {ratio: 100, owner: 'FullScreen', type: 'include', territories: ['US', 'CA']} }
12
+ it { expect(ownership.update general: [general_owner]).to be true }
13
+ end
14
+
15
+ describe 'the complete ownership can be obtained' do
16
+ before { ownership.release! }
17
+ it { expect(ownership.obtain!).to be true }
18
+ end
19
+
20
+ describe 'the complete ownership can be released' do
21
+ after { ownership.obtain! }
22
+ it { expect(ownership.release!).to be true }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,782 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'yt/models/channel'
4
+ require 'yt/models/playlist'
5
+
6
+ describe Yt::Playlist, :partner do
7
+ subject(:playlist) { Yt::Playlist.new id: id, auth: $content_owner }
8
+
9
+ context 'given a playlist of a partnered channel', :partner do
10
+ context 'managed by the authenticated Content Owner' do
11
+ let(:id) { ENV['YT_TEST_PARTNER_PLAYLIST_ID'] }
12
+
13
+ describe 'multiple reports can be retrieved at once' do
14
+ metrics = {views: Integer, estimated_minutes_watched: Integer,
15
+ average_view_duration: Integer, playlist_starts: Integer,
16
+ average_time_in_playlist: Float, views_per_playlist_start: Float}
17
+
18
+ specify 'by day' do
19
+ range = {since: 5.days.ago.to_date, until: 3.days.ago.to_date}
20
+ result = playlist.reports range.merge(only: metrics, by: :day)
21
+ metrics.each do |metric, type|
22
+ expect(result[metric].keys).to all(be_a Date)
23
+ expect(result[metric].values).to all(be_a type)
24
+ end
25
+ end
26
+
27
+ specify 'by month' do
28
+ result = playlist.reports only: metrics, by: :month, since: 1.month.ago
29
+ metrics.each do |metric, type|
30
+ expect(result[metric].keys).to all(be_a Range)
31
+ expect(result[metric].keys.map &:first).to all(be_a Date)
32
+ expect(result[metric].keys.map &:first).to eq result[metric].keys.map(&:first).map(&:beginning_of_month)
33
+ expect(result[metric].keys.map &:last).to all(be_a Date)
34
+ expect(result[metric].keys.map &:last).to eq result[metric].keys.map(&:last).map(&:end_of_month)
35
+ expect(result[metric].values).to all(be_a type)
36
+ end
37
+ end
38
+
39
+ specify 'by week' do
40
+ range = {since: ENV['YT_TEST_PARTNER_VIDEO_DATE'], until: Date.parse(ENV['YT_TEST_PARTNER_VIDEO_DATE']) + 9}
41
+ result = playlist.reports range.merge(only: metrics, by: :week)
42
+ metrics.each do |metric, type|
43
+ expect(result[metric].size).to be <= 2
44
+ expect(result[metric].keys).to all(be_a Range)
45
+ expect(result[metric].keys.map{|range| range.first.wday}.uniq).to be_one
46
+ expect(result[metric].keys.map{|range| range.last.wday}.uniq).to be_one
47
+ expect(result[metric].values).to all(be_a type)
48
+ end
49
+ end
50
+ end
51
+
52
+ {views: Integer, estimated_minutes_watched: Integer,
53
+ average_view_duration: Integer, playlist_starts: Integer,
54
+ average_time_in_playlist: Float,
55
+ views_per_playlist_start: Float}.each do |metric, type|
56
+ describe "#{metric} can be retrieved for a range of days" do
57
+ let(:date_in) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
58
+ let(:date_out) { Date.parse(ENV['YT_TEST_PARTNER_VIDEO_DATE']) + 5 }
59
+ let(:metric) { metric }
60
+ let(:result) { playlist.public_send metric, options }
61
+
62
+ context 'with a given start and end (:since/:until option)' do
63
+ let(:options) { {by: :day, since: date_in, until: date_out} }
64
+ specify do
65
+ expect(result.keys.min).to eq date_in.to_date
66
+ expect(result.keys.max).to eq date_out.to_date
67
+ end
68
+ end
69
+
70
+ context 'with a given start and end (:from/:to option)' do
71
+ let(:options) { {by: :day, from: date_in, to: date_out} }
72
+ specify do
73
+ expect(result.keys.min).to eq date_in.to_date
74
+ expect(result.keys.max).to eq date_out.to_date
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#{metric} can be grouped by month" do
80
+ let(:metric) { metric }
81
+ let(:result) { playlist.public_send metric, by: :month, since: 1.month.ago }
82
+ specify do
83
+ expect(result.keys).to all(be_a Range)
84
+ expect(result.keys.map &:first).to all(be_a Date)
85
+ expect(result.keys.map &:first).to eq result.keys.map(&:first).map(&:beginning_of_month)
86
+ expect(result.keys.map &:last).to all(be_a Date)
87
+ expect(result.keys.map &:last).to eq result.keys.map(&:last).map(&:end_of_month)
88
+ end
89
+ end
90
+
91
+ describe "#{metric} can be grouped by week and returns non-overlapping periods" do
92
+ let(:metric) { metric }
93
+ let(:range) { {since: ENV['YT_TEST_PARTNER_VIDEO_DATE'], until: Date.parse(ENV['YT_TEST_PARTNER_VIDEO_DATE']) + 9} }
94
+ let(:result) { playlist.public_send metric, range.merge(by: :week)}
95
+ specify do
96
+ expect(result.size).to be <= 2
97
+ expect(result.keys).to all(be_a Range)
98
+ expect(result.keys.map{|range| range.first.wday}.uniq).to be_one
99
+ expect(result.keys.map{|range| range.last.wday}.uniq).to be_one
100
+ end
101
+ end
102
+
103
+ describe "#{metric} can be retrieved for a specific day" do
104
+ let(:metric) { metric }
105
+ let(:result) { playlist.public_send "#{metric}_on", date }
106
+
107
+ context 'in which the playlist had data' do
108
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
109
+ it { expect(result).to be_a type }
110
+ end
111
+
112
+ context 'in the future' do
113
+ let(:date) { 5.days.from_now }
114
+ it { expect(result).to be_nil }
115
+ end
116
+ end
117
+
118
+ describe "#{metric} can be grouped by range" do
119
+ let(:metric) { metric }
120
+
121
+ context 'without a :by option (default)' do
122
+ let(:result) { playlist.public_send metric }
123
+ specify do
124
+ expect(result.size).to be 1
125
+ expect(result[:total]).to be_a type
126
+ end
127
+ end
128
+
129
+ context 'with the :by option set to :range' do
130
+ let(:result) { playlist.public_send metric, by: :range }
131
+ specify do
132
+ expect(result.size).to be 1
133
+ expect(result[:total]).to be_a type
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#{metric} can be retrieved for a single country" do
139
+ let(:result) { playlist.public_send metric, options }
140
+
141
+ context 'and grouped by day' do
142
+ let(:date_in) { 5.days.ago }
143
+ let(:options) { {by: :day, since: date_in, in: location} }
144
+
145
+ context 'with the :in option set to the country code' do
146
+ let(:location) { 'US' }
147
+ it { expect(result.keys.min).to eq date_in.to_date }
148
+ end
149
+
150
+ context 'with the :in option set to {country: country code}' do
151
+ let(:location) { {country: 'US'} }
152
+ it { expect(result.keys.min).to eq date_in.to_date }
153
+ end
154
+ end
155
+
156
+ context 'and grouped by country' do
157
+ let(:options) { {by: :country, in: location} }
158
+
159
+ context 'with the :in option set to the country code' do
160
+ let(:location) { 'US' }
161
+ it { expect(result.keys).to eq ['US'] }
162
+ end
163
+
164
+ context 'with the :in option set to {country: country code}' do
165
+ let(:location) { {country: 'US'} }
166
+ it { expect(result.keys).to eq ['US'] }
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ describe 'views can be retrieved for a single US state' do
173
+ let(:state_code) { 'CA' }
174
+ let(:result) { playlist.views since: date, by: by, in: location }
175
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
176
+
177
+ context 'and grouped by day' do
178
+ let(:by) { :day }
179
+
180
+ context 'with the :in option set to {state: state code}' do
181
+ let(:location) { {state: state_code} }
182
+ it { expect(result.keys.min).to eq date.to_date }
183
+ end
184
+
185
+ context 'with the :in option set to {country: "US", state: state code}' do
186
+ let(:location) { {country: 'US', state: state_code} }
187
+ it { expect(result.keys.min).to eq date.to_date }
188
+ end
189
+ end
190
+
191
+ context 'and grouped by US state' do
192
+ let(:by) { :state }
193
+
194
+ context 'with the :in option set to {state: state code}' do
195
+ let(:location) { {state: state_code} }
196
+ it { expect(result.keys).to eq [state_code] }
197
+ end
198
+
199
+ context 'with the :in option set to {country: "US", state: state code}' do
200
+ let(:location) { {country: 'US', state: state_code} }
201
+ it { expect(result.keys).to eq [state_code] }
202
+ end
203
+ end
204
+ end
205
+
206
+ describe 'views can be grouped by day' do
207
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
208
+ let(:keys) { range.values }
209
+
210
+ specify 'with the :by option set to :day' do
211
+ views = playlist.views range.merge by: :day
212
+ expect(views.keys).to eq range.values
213
+ end
214
+ end
215
+
216
+ describe 'views can be grouped by traffic source' do
217
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
218
+ let(:keys) { Yt::Collections::Reports::TRAFFIC_SOURCES.keys }
219
+
220
+ specify 'with the :by option set to :traffic_source' do
221
+ views = playlist.views range.merge by: :traffic_source
222
+ expect(views.keys - keys).to be_empty
223
+ end
224
+ end
225
+
226
+ describe 'views can be grouped by playback location' do
227
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
228
+ let(:keys) { Yt::Collections::Reports::PLAYBACK_LOCATIONS.keys }
229
+
230
+ specify 'with the :by option set to :playback_location' do
231
+ views = playlist.views range.merge by: :playback_location
232
+ expect(views.keys - keys).to be_empty
233
+ end
234
+ end
235
+
236
+ describe 'views can be grouped by related video' do
237
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
238
+
239
+ specify 'with the :by option set to :related_video' do
240
+ views = playlist.views range.merge by: :related_video
241
+ expect(views.keys).to all(be_instance_of Yt::Video)
242
+ end
243
+ end
244
+
245
+ describe 'views can be grouped by search term' do
246
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
247
+
248
+ specify 'with the :by option set to :search_term' do
249
+ views = playlist.views range.merge by: :search_term
250
+ expect(views.keys).to all(be_a String)
251
+ end
252
+ end
253
+
254
+ describe 'views can be grouped by referrer' do
255
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
256
+
257
+ specify 'with the :by option set to :referrer' do
258
+ views = playlist.views range.merge by: :referrer
259
+ expect(views.keys).to all(be_a String)
260
+ end
261
+ end
262
+
263
+ describe 'views can be grouped by video' do
264
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
265
+
266
+ specify 'with the :by option set to :video' do
267
+ views = playlist.views range.merge by: :video
268
+ expect(views.keys).to all(be_instance_of Yt::Video)
269
+ end
270
+ end
271
+
272
+ describe 'views can be grouped by playlist' do
273
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
274
+
275
+ specify 'with the :by option set to :playlist' do
276
+ views = playlist.views range.merge by: :playlist
277
+ expect(views.keys).to all(be_instance_of Yt::Playlist)
278
+ end
279
+ end
280
+
281
+ describe 'views can be grouped by device type' do
282
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
283
+
284
+ specify 'with the :by option set to :device_type' do
285
+ views = playlist.views range.merge by: :device_type
286
+ expect(views.keys).to all(be_instance_of Symbol)
287
+ expect(views.values).to all(be_an Integer)
288
+ end
289
+ end
290
+
291
+ describe 'views can be grouped by country' do
292
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
293
+
294
+ specify 'with the :by option set to :country' do
295
+ views = playlist.views range.merge by: :country
296
+ expect(views.keys).to all(be_a String)
297
+ expect(views.keys.map(&:length).uniq).to eq [2]
298
+ expect(views.values).to all(be_an Integer)
299
+ end
300
+ end
301
+
302
+ describe 'views can be grouped by state' do
303
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
304
+
305
+ specify 'with the :by option set to :state' do
306
+ views = playlist.views range.merge by: :state
307
+ expect(views.keys).to all(be_a String)
308
+ expect(views.keys.map(&:length).uniq).to eq [2]
309
+ expect(views.values).to all(be_an Integer)
310
+ end
311
+ end
312
+
313
+ describe 'estimated minutes watched can be retrieved for a single US state' do
314
+ let(:state_code) { 'CA' }
315
+ let(:result) { playlist.estimated_minutes_watched since: date, by: by, in: location }
316
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
317
+
318
+ context 'and grouped by day' do
319
+ let(:by) { :day }
320
+
321
+ context 'with the :in option set to {state: state code}' do
322
+ let(:location) { {state: state_code} }
323
+ it { expect(result.keys.min).to eq date.to_date }
324
+ end
325
+
326
+ context 'with the :in option set to {country: "US", state: state code}' do
327
+ let(:location) { {country: 'US', state: state_code} }
328
+ it { expect(result.keys.min).to eq date.to_date }
329
+ end
330
+ end
331
+
332
+ context 'and grouped by US state' do
333
+ let(:by) { :state }
334
+
335
+ context 'with the :in option set to {state: state code}' do
336
+ let(:location) { {state: state_code} }
337
+ it { expect(result.keys).to eq [state_code] }
338
+ end
339
+
340
+ context 'with the :in option set to {country: "US", state: state code}' do
341
+ let(:location) { {country: 'US', state: state_code} }
342
+ it { expect(result.keys).to eq [state_code] }
343
+ end
344
+ end
345
+ end
346
+
347
+ describe 'estimated minutes watched can be grouped by day' do
348
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
349
+ let(:keys) { range.values }
350
+
351
+ specify 'with the :by option set to :day' do
352
+ minutes = playlist.estimated_minutes_watched range.merge by: :day
353
+ expect(minutes.keys).to eq range.values
354
+ end
355
+ end
356
+
357
+ describe 'estimated minutes watched can be grouped by traffic source' do
358
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
359
+ let(:keys) { Yt::Collections::Reports::TRAFFIC_SOURCES.keys }
360
+
361
+ specify 'with the :by option set to :traffic_source' do
362
+ minutes = playlist.estimated_minutes_watched range.merge by: :traffic_source
363
+ expect(minutes.keys - keys).to be_empty
364
+ end
365
+ end
366
+
367
+ describe 'estimated minutes watched can be grouped by playback location' do
368
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
369
+ let(:keys) { Yt::Collections::Reports::PLAYBACK_LOCATIONS.keys }
370
+
371
+ specify 'with the :by option set to :playback_location' do
372
+ minutes = playlist.estimated_minutes_watched range.merge by: :playback_location
373
+ expect(minutes.keys - keys).to be_empty
374
+ end
375
+ end
376
+
377
+ describe 'estimated minutes watched can be grouped by related video' do
378
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
379
+
380
+ specify 'with the :by option set to :related_video' do
381
+ minutes = playlist.estimated_minutes_watched range.merge by: :related_video
382
+ expect(minutes.keys).to all(be_instance_of Yt::Video)
383
+ end
384
+ end
385
+
386
+ describe 'estimated minutes watched can be grouped by search term' do
387
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
388
+
389
+ specify 'with the :by option set to :search_term' do
390
+ minutes = playlist.estimated_minutes_watched range.merge by: :search_term
391
+ expect(minutes.keys).to all(be_a String)
392
+ end
393
+ end
394
+
395
+ describe 'estimated minutes watched can be grouped by referrer' do
396
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
397
+
398
+ specify 'with the :by option set to :referrer' do
399
+ minutes = playlist.estimated_minutes_watched range.merge by: :referrer
400
+ expect(minutes.keys).to all(be_a String)
401
+ end
402
+ end
403
+
404
+ describe 'estimated minutes watched can be grouped by video' do
405
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
406
+
407
+ specify 'with the :by option set to :video' do
408
+ minutes = playlist.estimated_minutes_watched range.merge by: :video
409
+ expect(minutes.keys).to all(be_instance_of Yt::Video)
410
+ end
411
+ end
412
+
413
+ describe 'estimated minutes watched can be grouped by playlist' do
414
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
415
+
416
+ specify 'with the :by option set to :playlist' do
417
+ minutes = playlist.estimated_minutes_watched range.merge by: :playlist
418
+ expect(minutes.keys).to all(be_instance_of Yt::Playlist)
419
+ end
420
+ end
421
+
422
+ describe 'estimated minutes watched can be grouped by device type' do
423
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
424
+
425
+ specify 'with the :by option set to :device_type' do
426
+ minutes = playlist.estimated_minutes_watched range.merge by: :device_type
427
+ expect(minutes.keys).to all(be_instance_of Symbol)
428
+ expect(minutes.values).to all(be_an Integer)
429
+ end
430
+ end
431
+
432
+ describe 'estimated minutes watched can be grouped by country' do
433
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
434
+
435
+ specify 'with the :by option set to :country' do
436
+ minutes = playlist.estimated_minutes_watched range.merge by: :country
437
+ expect(minutes.keys).to all(be_a String)
438
+ expect(minutes.keys.map(&:length).uniq).to eq [2]
439
+ expect(minutes.values).to all(be_an Integer)
440
+ end
441
+ end
442
+
443
+ describe 'estimated minutes watched can be grouped by state' do
444
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
445
+
446
+ specify 'with the :by option set to :state' do
447
+ minutes = playlist.estimated_minutes_watched range.merge by: :state
448
+ expect(minutes.keys).to all(be_a String)
449
+ expect(minutes.keys.map(&:length).uniq).to eq [2]
450
+ expect(minutes.values).to all(be_an Integer)
451
+ end
452
+ end
453
+
454
+ describe 'viewer percentage can be retrieved for a range of days' do
455
+ let(:viewer_percentage) { playlist.viewer_percentage since: 1.year.ago, until: 10.days.ago}
456
+ it { expect(viewer_percentage).to be_a Hash }
457
+ end
458
+
459
+ describe 'viewer_percentage can be grouped by gender and age group' do
460
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
461
+ let(:keys) { range.values }
462
+
463
+ specify 'without a :by option (default)' do
464
+ viewer_percentage = playlist.viewer_percentage range
465
+ expect(viewer_percentage.keys).to match_array [:female, :male]
466
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
467
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
468
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
469
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
470
+ end
471
+
472
+ specify 'with the :by option set to :gender_age_group' do
473
+ viewer_percentage = playlist.viewer_percentage range.merge by: :gender_age_group
474
+ expect(viewer_percentage.keys).to match_array [:female, :male]
475
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
476
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
477
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
478
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
479
+ end
480
+ end
481
+
482
+ describe 'viewer_percentage can be grouped by gender' do
483
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
484
+ let(:keys) { range.values }
485
+
486
+ specify 'with the :by option set to :gender' do
487
+ viewer_percentage = playlist.viewer_percentage range.merge by: :gender
488
+ expect(viewer_percentage.keys).to match_array [:female, :male]
489
+ expect(viewer_percentage[:female]).to be_a Float
490
+ expect(viewer_percentage[:male]).to be_a Float
491
+ end
492
+ end
493
+
494
+ describe 'viewer_percentage can be grouped by age group' do
495
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
496
+ let(:keys) { range.values }
497
+
498
+ specify 'with the :by option set to :age_group' do
499
+ viewer_percentage = playlist.viewer_percentage range.merge by: :age_group
500
+ expect(viewer_percentage.keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
501
+ expect(viewer_percentage.values).to all(be_instance_of Float)
502
+ end
503
+ end
504
+
505
+
506
+ describe 'average view duration can be retrieved for a single US state' do
507
+ let(:state_code) { 'CA' }
508
+ let(:result) { playlist.average_view_duration since: date, by: by, in: location }
509
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
510
+
511
+ context 'and grouped by day' do
512
+ let(:by) { :day }
513
+
514
+ context 'with the :in option set to {state: state code}' do
515
+ let(:location) { {state: state_code} }
516
+ it { expect(result.keys.min).to eq date.to_date }
517
+ end
518
+
519
+ context 'with the :in option set to {country: "US", state: state code}' do
520
+ let(:location) { {country: 'US', state: state_code} }
521
+ it { expect(result.keys.min).to eq date.to_date }
522
+ end
523
+ end
524
+
525
+ context 'and grouped by US state' do
526
+ let(:by) { :state }
527
+
528
+ context 'with the :in option set to {state: state code}' do
529
+ let(:location) { {state: state_code} }
530
+ it { expect(result.keys).to eq [state_code] }
531
+ end
532
+
533
+ context 'with the :in option set to {country: "US", state: state code}' do
534
+ let(:location) { {country: 'US', state: state_code} }
535
+ it { expect(result.keys).to eq [state_code] }
536
+ end
537
+ end
538
+ end
539
+
540
+ describe 'average view duration can be grouped by day' do
541
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
542
+ let(:keys) { range.values }
543
+
544
+ specify 'with the :by option set to :day' do
545
+ average_view_duration = playlist.average_view_duration range.merge by: :day
546
+ expect(average_view_duration.keys).to eq range.values
547
+ end
548
+ end
549
+
550
+ describe 'average view duration can be grouped by country' do
551
+ let(:range) { {since: 4.days.ago, until: 3.days.ago} }
552
+
553
+ specify 'with the :by option set to :country' do
554
+ duration = playlist.average_view_duration range.merge by: :country
555
+ expect(duration.keys).to all(be_a String)
556
+ expect(duration.keys.map(&:length).uniq).to eq [2]
557
+ expect(duration.values).to all(be_an Integer)
558
+ end
559
+ end
560
+
561
+ describe 'average view duration can be grouped by state' do
562
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
563
+
564
+ specify 'with the :by option set to :state' do
565
+ duration = playlist.average_view_duration range.merge by: :state
566
+ expect(duration.keys).to all(be_a String)
567
+ expect(duration.keys.map(&:length).uniq).to eq [2]
568
+ expect(duration.values).to all(be_an Integer)
569
+ end
570
+ end
571
+
572
+ describe 'playlist starts can be retrieved for a single US state' do
573
+ let(:state_code) { 'CA' }
574
+ let(:result) { playlist.playlist_starts since: date, by: by, in: location }
575
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
576
+
577
+ context 'and grouped by day' do
578
+ let(:by) { :day }
579
+
580
+ context 'with the :in option set to {state: state code}' do
581
+ let(:location) { {state: state_code} }
582
+ it { expect(result.keys.min).to eq date.to_date }
583
+ end
584
+
585
+ context 'with the :in option set to {country: "US", state: state code}' do
586
+ let(:location) { {country: 'US', state: state_code} }
587
+ it { expect(result.keys.min).to eq date.to_date }
588
+ end
589
+ end
590
+
591
+ context 'and grouped by US state' do
592
+ let(:by) { :state }
593
+
594
+ context 'with the :in option set to {state: state code}' do
595
+ let(:location) { {state: state_code} }
596
+ it { expect(result.keys).to eq [state_code] }
597
+ end
598
+
599
+ context 'with the :in option set to {country: "US", state: state code}' do
600
+ let(:location) { {country: 'US', state: state_code} }
601
+ it { expect(result.keys).to eq [state_code] }
602
+ end
603
+ end
604
+ end
605
+
606
+ describe 'playlist starts can be grouped by day' do
607
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
608
+ let(:keys) { range.values }
609
+
610
+ specify 'with the :by option set to :day' do
611
+ playlist_starts = playlist.playlist_starts range.merge by: :day
612
+ expect(playlist_starts.keys).to eq range.values
613
+ end
614
+ end
615
+
616
+ describe 'playlist starts can be grouped by country' do
617
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
618
+
619
+ specify 'with the :by option set to :country' do
620
+ starts = playlist.playlist_starts range.merge by: :country
621
+ expect(starts.keys).to all(be_a String)
622
+ expect(starts.keys.map(&:length).uniq).to eq [2]
623
+ expect(starts.values).to all(be_an Integer)
624
+ end
625
+ end
626
+
627
+ describe 'playlist starts can be grouped by state' do
628
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
629
+
630
+ specify 'with the :by option set to :state' do
631
+ starts = playlist.playlist_starts range.merge by: :state
632
+ expect(starts.keys).to all(be_a String)
633
+ expect(starts.keys.map(&:length).uniq).to eq [2]
634
+ expect(starts.values).to all(be_an Integer)
635
+ end
636
+ end
637
+
638
+ describe 'average time in playlist can be retrieved for a single US state' do
639
+ let(:state_code) { 'CA' }
640
+ let(:result) { playlist.average_time_in_playlist since: date, by: by, in: location }
641
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
642
+
643
+ context 'and grouped by day' do
644
+ let(:by) { :day }
645
+
646
+ context 'with the :in option set to {state: state code}' do
647
+ let(:location) { {state: state_code} }
648
+ it { expect(result.keys.min).to eq date.to_date }
649
+ end
650
+
651
+ context 'with the :in option set to {country: "US", state: state code}' do
652
+ let(:location) { {country: 'US', state: state_code} }
653
+ it { expect(result.keys.min).to eq date.to_date }
654
+ end
655
+ end
656
+
657
+ context 'and grouped by US state' do
658
+ let(:by) { :state }
659
+
660
+ context 'with the :in option set to {state: state code}' do
661
+ let(:location) { {state: state_code} }
662
+ it { expect(result.keys).to eq [state_code] }
663
+ end
664
+
665
+ context 'with the :in option set to {country: "US", state: state code}' do
666
+ let(:location) { {country: 'US', state: state_code} }
667
+ it { expect(result.keys).to eq [state_code] }
668
+ end
669
+ end
670
+ end
671
+
672
+ describe 'average time in playlist can be grouped by day' do
673
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
674
+ let(:keys) { range.values }
675
+
676
+ specify 'with the :by option set to :day' do
677
+ average_time_in_playlist = playlist.average_time_in_playlist range.merge by: :day
678
+ expect(average_time_in_playlist.keys).to eq range.values
679
+ end
680
+ end
681
+
682
+ describe 'average time in playlist can be grouped by country' do
683
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
684
+
685
+ specify 'with the :by option set to :country' do
686
+ time = playlist.average_time_in_playlist range.merge by: :country
687
+ expect(time.keys).to all(be_a String)
688
+ expect(time.keys.map(&:length).uniq).to eq [2]
689
+ expect(time.values).to all(be_a Float)
690
+ end
691
+ end
692
+
693
+ describe 'average time in playlist can be grouped by state' do
694
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
695
+
696
+ specify 'with the :by option set to :state' do
697
+ time = playlist.average_time_in_playlist range.merge by: :state
698
+ expect(time.keys).to all(be_a String)
699
+ expect(time.keys.map(&:length).uniq).to eq [2]
700
+ expect(time.values).to all(be_a Float)
701
+ end
702
+ end
703
+
704
+ describe 'views per playlists start can be retrieved for a single US state' do
705
+ let(:state_code) { 'CA' }
706
+ let(:result) { playlist.views_per_playlist_start since: date, by: by, in: location }
707
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
708
+
709
+ context 'and grouped by day' do
710
+ let(:by) { :day }
711
+
712
+ context 'with the :in option set to {state: state code}' do
713
+ let(:location) { {state: state_code} }
714
+ it { expect(result.keys.min).to eq date.to_date }
715
+ end
716
+
717
+ context 'with the :in option set to {country: "US", state: state code}' do
718
+ let(:location) { {country: 'US', state: state_code} }
719
+ it { expect(result.keys.min).to eq date.to_date }
720
+ end
721
+ end
722
+
723
+ context 'and grouped by US state' do
724
+ let(:by) { :state }
725
+
726
+ context 'with the :in option set to {state: state code}' do
727
+ let(:location) { {state: state_code} }
728
+ it { expect(result.keys).to eq [state_code] }
729
+ end
730
+
731
+ context 'with the :in option set to {country: "US", state: state code}' do
732
+ let(:location) { {country: 'US', state: state_code} }
733
+ it { expect(result.keys).to eq [state_code] }
734
+ end
735
+ end
736
+ end
737
+
738
+ describe 'views per playlist start can be grouped by day' do
739
+ let(:range) { {since: 4.days.ago.to_date, until: 3.days.ago.to_date} }
740
+ let(:keys) { range.values }
741
+
742
+ specify 'with the :by option set to :day' do
743
+ views_per_playlist_start = playlist.views_per_playlist_start range.merge by: :day
744
+ expect(views_per_playlist_start.keys).to eq range.values
745
+ end
746
+ end
747
+
748
+ describe 'views per playlist start can be grouped by country' do
749
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
750
+
751
+ specify 'with the :by option set to :country' do
752
+ views = playlist.views_per_playlist_start range.merge by: :country
753
+ expect(views.keys).to all(be_a String)
754
+ expect(views.keys.map(&:length).uniq).to eq [2]
755
+ expect(views.values).to all(be_a Float)
756
+ end
757
+ end
758
+
759
+ describe 'views per playlist start can be grouped by state' do
760
+ let(:range) { {since: ENV['YT_TEST_PARTNER_PLAYLIST_DATE']} }
761
+
762
+ specify 'with the :by option set to :state' do
763
+ views = playlist.views_per_playlist_start range.merge by: :state
764
+ expect(views.keys).to all(be_a String)
765
+ expect(views.keys.map(&:length).uniq).to eq [2]
766
+ expect(views.values).to all(be_a Float)
767
+ end
768
+ end
769
+ end
770
+
771
+ context 'not managed by the authenticated Content Owner' do
772
+ let(:id) { 'PLbpi6ZahtOH6J5oPGySZcmbRfT7Hyq1sZ' }
773
+
774
+ specify 'playlist starts cannot be retrieved' do
775
+ expect{playlist.views}.to raise_error Yt::Errors::Forbidden
776
+ expect{playlist.playlist_starts}.to raise_error Yt::Errors::Forbidden
777
+ expect{playlist.average_time_in_playlist}.to raise_error Yt::Errors::Forbidden
778
+ expect{playlist.views_per_playlist_start}.to raise_error Yt::Errors::Forbidden
779
+ end
780
+ end
781
+ end
782
+ end