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,30 @@
1
+ require 'spec_helper'
2
+ require 'yt/collections/reports'
3
+ require 'yt/models/content_owner'
4
+
5
+ describe Yt::Collections::Reports do
6
+ subject(:reports) { Yt::Collections::Reports.new(parent: content_owner).tap{|reports| reports.metrics = {views: Integer}} }
7
+ let(:content_owner) { Yt::ContentOwner.new owner_name: 'any-name' }
8
+ let(:error){ {reason: reason, message: message} }
9
+ let(:msg) { {response_body: {error: {errors: [error]}}}.to_json }
10
+
11
+ describe '#within' do
12
+ let(:result) { reports.within Range.new(5.days.ago, 4.days.ago), nil, nil, :day, nil }
13
+ context 'given the request raises error 400 with "Invalid Query" message' do
14
+ let(:reason) { 'badRequest' }
15
+ let(:message) { 'Invalid query. Query did not conform to the expectations' }
16
+ before { expect(reports).to receive(:list).once.and_raise Yt::Error, msg }
17
+ let(:try_again) { expect(reports).to receive(:list).at_least(:once) }
18
+
19
+ context 'every time' do
20
+ before { try_again.and_raise Yt::Error, msg }
21
+ it { expect{result}.to fail }
22
+ end
23
+
24
+ context 'but returns a success code 2XX the second time' do
25
+ before { try_again.and_return [views: {total: 20}] }
26
+ it { expect{result}.not_to fail }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'yt/collections/subscriptions'
3
+
4
+ describe Yt::Collections::Subscriptions do
5
+ subject(:collection) { Yt::Collections::Subscriptions.new }
6
+ let(:msg) { {response_body: {error: {errors: [{reason: reason}]}}}.to_json }
7
+ before { expect(collection).to behave }
8
+
9
+ describe '#insert' do
10
+ context 'given a new subscription' do
11
+ let(:subscription) { Yt::Subscription.new }
12
+ let(:behave) { receive(:do_insert).and_return subscription }
13
+
14
+ it { expect(collection.insert).to eq subscription }
15
+ end
16
+
17
+ context 'given a duplicate subscription' do
18
+ let(:reason) { 'subscriptionDuplicate' }
19
+ let(:behave) { receive(:do_insert).and_raise Yt::Error, msg }
20
+
21
+ it { expect{collection.insert}.to fail.with 'subscriptionDuplicate' }
22
+ it { expect{collection.insert ignore_errors: true}.not_to fail }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'yt/collections/videos'
3
+ require 'yt/models/channel'
4
+
5
+ describe Yt::Collections::Videos do
6
+ subject(:collection) { Yt::Collections::Videos.new parent: channel }
7
+ let(:channel) { Yt::Channel.new id: 'any-id' }
8
+ let(:page) { {items: [], token: 'any-token'} }
9
+
10
+ describe '#size', :ruby2 do
11
+ describe 'sends only one request and return the total results' do
12
+ let(:total_results) { 123456 }
13
+ before do
14
+ expect_any_instance_of(Yt::Request).to receive(:run).once do
15
+ double(body: {'pageInfo'=>{'totalResults'=>total_results}})
16
+ end
17
+ end
18
+ it { expect(collection.size).to be total_results }
19
+ end
20
+ end
21
+
22
+ describe '#count' do
23
+ let(:query) { {q: 'search string'} }
24
+
25
+ context 'called once with .where(query) and once without' do
26
+ after do
27
+ collection.where(query).count
28
+ collection.count
29
+ end
30
+
31
+ it 'only applies the query on the first call' do
32
+ expect(collection).to receive(:fetch_page) do |options|
33
+ expect(options[:params]).to include query
34
+ page
35
+ end
36
+ expect(collection).to receive(:fetch_page) do |options|
37
+ expect(options[:params]).not_to include query
38
+ page
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/forbidden'
3
+
4
+ describe Yt::Errors::Forbidden do
5
+ let(:msg) { %r{^A request to YouTube API was considered forbidden by the server} }
6
+
7
+ describe '#exception' do
8
+ it { expect{raise Yt::Errors::Forbidden}.to raise_error msg }
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/missing_auth'
3
+
4
+ describe Yt::Errors::MissingAuth do
5
+ subject(:error) { raise Yt::Errors::MissingAuth, params }
6
+ let(:params) { {} }
7
+ let(:msg) { %r{^A request to YouTube API was sent without a valid authentication} }
8
+
9
+ describe '#exception' do
10
+ it { expect{error}.to raise_error msg }
11
+
12
+ context 'given the user can authenticate via web' do
13
+ let(:params) { {scopes: 'youtube', authentication_url: 'http://google.example.com/auth', redirect_uri: 'http://localhost/'} }
14
+ let(:msg) { %r{^You can ask YouTube accounts to authenticate your app} }
15
+ it { expect{error}.to raise_error msg }
16
+ end
17
+
18
+ context 'given the user can authenticate via device code' do
19
+ let(:params) { {scopes: 'youtube', user_code: 'abcdefgh', verification_url: 'http://google.com/device'} }
20
+ let(:msg) { %r{^Please authenticate your app by visiting the page} }
21
+ it { expect{error}.to raise_error msg }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/no_items'
3
+
4
+ describe Yt::Errors::NoItems do
5
+ let(:msg) { %r{^A request to YouTube API returned no items} }
6
+
7
+ describe '#exception' do
8
+ it { expect{raise Yt::Errors::NoItems}.to raise_error msg }
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/request_error'
3
+
4
+ describe Yt::Errors::RequestError do
5
+ subject(:error) { raise Yt::Errors::RequestError, params }
6
+ let(:params) { {} }
7
+ let(:msg) { %r{^A request to YouTube API failed} }
8
+
9
+ describe '#exception' do
10
+ it { expect{error}.to raise_error msg }
11
+
12
+ context 'given the exception includes sensitive data' do
13
+ let(:body) { 'some secret token' }
14
+ let(:curl) { 'curl -H "Authorization: secret-token"' }
15
+ let(:params) { {response_body: body, request_curl: curl}.to_json }
16
+
17
+ describe 'given a log level of :debug or :devel' do
18
+ before(:all) { Yt.configuration.log_level = :debug }
19
+ specify 'exposes sensitive data' do
20
+ expect{error}.to raise_error do |error|
21
+ expect(error.message).to include 'secret'
22
+ end
23
+ end
24
+ end
25
+
26
+ describe 'given a different log level' do
27
+ before(:all) { Yt.configuration.log_level = :info }
28
+ specify 'hides sensitive data' do
29
+ expect{error}.to raise_error do |error|
30
+ expect(error.message).not_to include 'secret'
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe Yt::Error do
39
+ let(:msg) { %r{^A request to YouTube API failed} }
40
+
41
+ describe '#exception' do
42
+ it { expect{raise Yt::Error}.to raise_error msg }
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/server_error'
3
+
4
+ describe Yt::Errors::ServerError do
5
+ let(:msg) { %r{^A request to YouTube API caused an unexpected server error} }
6
+
7
+ describe '#exception' do
8
+ it { expect{raise Yt::Errors::ServerError}.to raise_error msg }
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'yt/errors/unauthorized'
3
+
4
+ describe Yt::Errors::Unauthorized do
5
+ let(:msg) { %r{^A request to YouTube API was sent without a valid authentication} }
6
+
7
+ describe '#exception' do
8
+ it { expect{raise Yt::Errors::Unauthorized}.to raise_error msg }
9
+ end
10
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/account'
3
+
4
+ describe Yt::Video do
5
+ subject(:account) { Yt::Account.new attrs }
6
+
7
+ describe '#id' do
8
+ context 'given a user info with an ID' do
9
+ let(:attrs) { {user_info: {"id"=>"103024385"}} }
10
+ it { expect(account.id).to eq '103024385' }
11
+ end
12
+
13
+ context 'given a user info without an ID' do
14
+ let(:attrs) { {user_info: {}} }
15
+ it { expect(account.id).to eq '' }
16
+ end
17
+ end
18
+
19
+ describe '#email' do
20
+ context 'given a user info with an email' do
21
+ let(:attrs) { {user_info: {"email"=>"user@example.com"}} }
22
+ it { expect(account.email).to eq 'user@example.com' }
23
+ end
24
+
25
+ context 'given a user info without an email' do
26
+ let(:attrs) { {user_info: {}} }
27
+ it { expect(account.email).to eq '' }
28
+ end
29
+ end
30
+
31
+ describe '#has_verified_email?' do
32
+ context 'given a user info with a verified email' do
33
+ let(:attrs) { {user_info: {"verified_email"=>true}} }
34
+ it { expect(account).to have_verified_email }
35
+ end
36
+
37
+ context 'given a user info without a verified email' do
38
+ let(:attrs) { {user_info: {"verified_email"=>false}} }
39
+ it { expect(account).not_to have_verified_email }
40
+ end
41
+ end
42
+
43
+ describe '#name' do
44
+ context 'given a user info with a name' do
45
+ let(:attrs) { {user_info: {"name"=>"User Example"}} }
46
+ it { expect(account.name).to eq 'User Example' }
47
+ end
48
+
49
+ context 'given a user info without a name' do
50
+ let(:attrs) { {user_info: {}} }
51
+ it { expect(account.name).to eq '' }
52
+ end
53
+ end
54
+
55
+ describe '#given_name' do
56
+ context 'given a user info with a given name' do
57
+ let(:attrs) { {user_info: {"given_name"=>"User"}} }
58
+ it { expect(account.given_name).to eq 'User' }
59
+ end
60
+
61
+ context 'given a user info without a given name' do
62
+ let(:attrs) { {user_info: {}} }
63
+ it { expect(account.given_name).to eq '' }
64
+ end
65
+ end
66
+
67
+ describe '#family_name' do
68
+ context 'given a user info with a family name' do
69
+ let(:attrs) { {user_info: {"family_name"=>"Example"}} }
70
+ it { expect(account.family_name).to eq 'Example' }
71
+ end
72
+
73
+ context 'given a user info without a family name' do
74
+ let(:attrs) { {user_info: {}} }
75
+ it { expect(account.family_name).to eq '' }
76
+ end
77
+ end
78
+
79
+ describe '#profile_url' do
80
+ context 'given a user info with a link' do
81
+ let(:attrs) { {user_info: {"link"=>"https://plus.google.com/1234"}} }
82
+ it { expect(account.profile_url).to eq 'https://plus.google.com/1234' }
83
+ end
84
+
85
+ context 'given a user info without a link' do
86
+ let(:attrs) { {user_info: {}} }
87
+ it { expect(account.profile_url).to eq '' }
88
+ end
89
+ end
90
+
91
+ describe '#avatar_url' do
92
+ context 'given a user info with a picture' do
93
+ let(:attrs) { {user_info: {"picture"=>"https://lh3.googleusercontent.com/photo.jpg"}} }
94
+ it { expect(account.avatar_url).to eq 'https://lh3.googleusercontent.com/photo.jpg' }
95
+ end
96
+
97
+ context 'given a user info without a picture' do
98
+ let(:attrs) { {user_info: {}} }
99
+ it { expect(account.avatar_url).to eq '' }
100
+ end
101
+ end
102
+
103
+ describe '#gender' do
104
+ context 'given a user info with a gender' do
105
+ let(:attrs) { {user_info: {"gender"=>"male"}} }
106
+ it { expect(account.gender).to eq 'male' }
107
+ end
108
+
109
+ context 'given a user info without a gender' do
110
+ let(:attrs) { {user_info: {}} }
111
+ it { expect(account.gender).to eq '' }
112
+ end
113
+ end
114
+
115
+ describe '#locale' do
116
+ context 'given a user info with a locale' do
117
+ let(:attrs) { {user_info: {"locale"=>"en"}} }
118
+ it { expect(account.locale).to eq 'en' }
119
+ end
120
+
121
+ context 'given a user info without a locale' do
122
+ let(:attrs) { {user_info: {}} }
123
+ it { expect(account.locale).to eq '' }
124
+ end
125
+ end
126
+
127
+ describe '#hd' do
128
+ context 'given a user info with a Google App domain' do
129
+ let(:attrs) { {user_info: {"hd"=>"example.com"}} }
130
+ it { expect(account.hd).to eq 'example.com' }
131
+ end
132
+
133
+ context 'given a user info without a Google App domain' do
134
+ let(:attrs) { {user_info: {}} }
135
+ it { expect(account.hd).to eq '' }
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/annotation'
3
+
4
+ describe Yt::Annotation do
5
+ subject(:annotation) { Yt::Annotation.new data: Hash.from_xml(xml) }
6
+
7
+ describe '#above? and #below?' do
8
+ let(:xml) { %Q{
9
+ <segment>
10
+ <movingRegion type="rect">
11
+ <rectRegion d="0" h="17.7779998779" t="0:04.000" w="25.0" x="7.117000103" y="5.07000017166"/>
12
+ <rectRegion d="0" h="17.7779998779" t="0:05.000" w="25.0" x="7.117000103" y="5.07000017166"/>
13
+ </movingRegion>
14
+ </segment>
15
+ } }
16
+
17
+ context 'given an annotation located above N% of the video height' do
18
+ it { expect(annotation.above? 50).to be true }
19
+ it { expect(annotation.below? 50).to be_falsey }
20
+ end
21
+
22
+ context 'given an annotation located below N% of the video height' do
23
+ it { expect(annotation.above? 5).to be_falsey }
24
+ it { expect(annotation.below? 5).to be true }
25
+ end
26
+
27
+ context 'given an annotation without a single position' do
28
+ let(:xml) { %Q{
29
+ <segment>
30
+ <movingRegion type="rect">
31
+ <rectRegion d="0" h="17.7779998779" t="0:05.000" w="25.0" x="7.117000103" y="5.07000017166"/>
32
+ </movingRegion>
33
+ </segment>
34
+ } }
35
+ it { expect(annotation.above? 50).to be true }
36
+ it { expect(annotation.below? 50).to be_falsey }
37
+ end
38
+
39
+ context 'given an annotation without explicit location' do
40
+ let(:xml) { '<segment></segment>' }
41
+ it { expect(annotation.above? 50).to be_falsey }
42
+ it { expect(annotation.below? 50).to be_falsey }
43
+ end
44
+ end
45
+
46
+ describe '#text' do
47
+ context 'given an annotation with text' do
48
+ let(:xml) { '<TEXT>Hello, world!</TEXT>' }
49
+ it { expect(annotation.text).to eq 'Hello, world!' }
50
+ end
51
+ end
52
+
53
+ describe '#has_link_to_subscribe?' do
54
+ context 'given an annotation with a link of class 5' do
55
+ let(:xml) { '<action type="openUrl"><url link_class="5"/></action>' }
56
+ it { expect(annotation).to have_link_to_subscribe }
57
+ end
58
+
59
+ context 'given an annotation without a link of class 5' do
60
+ let(:xml) { '<action type="openUrl"><url link_class="3"/></action>' }
61
+ it { expect(annotation).not_to have_link_to_subscribe }
62
+ end
63
+ end
64
+
65
+ describe '#has_link_to_video?' do
66
+ context 'given an annotation with a link of class 1' do
67
+ let(:xml) { '<action type="openUrl"><url link_class="1"/></action>' }
68
+ it { expect(annotation).to have_link_to_video }
69
+ end
70
+
71
+ context 'given an annotation with a "featured video" invideo programming' do
72
+ let(:xml) { '<type>promotion</type>' }
73
+ it { expect(annotation).to have_link_to_video }
74
+ end
75
+
76
+ context 'given an annotation without a link of class 1' do
77
+ let(:xml) { '<action type="openUrl"><url link_class="3"/></action>' }
78
+ it { expect(annotation).not_to have_link_to_video }
79
+ end
80
+ end
81
+
82
+ describe '#has_link_to_playlist?' do
83
+ context 'given an annotation with a link of class 2' do
84
+ let(:xml) { '<action type="openUrl"><url link_class="2"/></action>' }
85
+ it { expect(annotation).to have_link_to_playlist }
86
+ end
87
+
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>' }
90
+ it { expect(annotation).to have_link_to_playlist }
91
+ end
92
+
93
+ context 'given an annotation without a link of class 2' do
94
+ let(:xml) { '<action type="openUrl"><url link_class="3"/></action>' }
95
+ it { expect(annotation).not_to have_link_to_playlist }
96
+ end
97
+
98
+ context 'given an annotation without text' do
99
+ let(:xml) { '<TEXT />' }
100
+ it { expect(annotation).not_to have_link_to_playlist }
101
+ end
102
+ end
103
+
104
+ describe '#has_link_to_same_window?' do
105
+ context 'given an annotation with a "current" target' do
106
+ let(:xml) { '<action type="openUrl"><url target="current"/></action>' }
107
+ it { expect(annotation).to have_link_to_same_window }
108
+ end
109
+
110
+ context 'given an annotation without a "current" target' do
111
+ let(:xml) { '<action type="openUrl"><url target="new"/></action>' }
112
+ it { expect(annotation).not_to have_link_to_same_window }
113
+ end
114
+ end
115
+
116
+ describe '#has_invideo_programming?' do
117
+ context 'given an annotation with a "featured video" invideo programming' do
118
+ let(:xml) { '<type>promotion</type>' }
119
+ it { expect(annotation).to have_invideo_programming }
120
+ end
121
+
122
+ context 'given an annotation with a "branding intro" invideo programming' do
123
+ let(:xml) { '<type>branding</type>' }
124
+ it { expect(annotation).to have_invideo_programming }
125
+ end
126
+
127
+ context 'given an annotation without an invideo programming' do
128
+ let(:xml) { '<segment></segment>' }
129
+ it { expect(annotation).not_to have_invideo_programming }
130
+ end
131
+ end
132
+
133
+ describe '#starts_after and #starts_before' do
134
+ context 'given an annotation with the first timestamp equal to 1 hour' do
135
+ let(:xml) { %Q{
136
+ <segment>
137
+ <movingRegion type="rect">
138
+ <rectRegion d="0" h="17.7779998779" t="01:00:01.000" w="25.0" x="7.117000103" y="5.07000017166"/>
139
+ <rectRegion d="0" h="17.7779998779" t="01:05:56.066" w="25.0" x="7.117000103" y="5.07000017166"/>
140
+ </movingRegion>
141
+ </segment>
142
+ } }
143
+ it { expect(annotation.starts_after? 3600).to be true }
144
+ it { expect(annotation.starts_after? 3610).to be_falsey }
145
+ it { expect(annotation.starts_before? 3600).to be_falsey }
146
+ it { expect(annotation.starts_before? 3610).to be true }
147
+ end
148
+
149
+ context 'given an annotation without timestamps' do
150
+ let(:xml) { '<segment></segment>' }
151
+ it { expect(annotation.starts_after? 0).to be_nil }
152
+ it { expect(annotation.starts_before? 0).to be_nil }
153
+ end
154
+
155
+ context 'given an annotation with "never" as the timestamp' do
156
+ let(:xml) { %Q{
157
+ <segment>
158
+ <movingRegion type="rect">
159
+ <rectRegion h="6.0" t="never" w="25.0" x="7.0" y="5.0"/>
160
+ <rectRegion h="6.0" t="never" w="25.0" x="7.0" y="5.0"/>
161
+ </movingRegion>
162
+ </segment>
163
+ } }
164
+ it { expect(annotation.starts_after? 0).to be_nil }
165
+ it { expect(annotation.starts_before? 0).to be_nil }
166
+ end
167
+
168
+ context 'given an annotation with "0" as the timestamp' do
169
+ let(:xml) { %Q{
170
+ <segment>
171
+ <movingRegion type="rect">
172
+ <rectRegion h="6.0" t="0" w="25.0" x="7.0" y="5.0"/>
173
+ </movingRegion>
174
+ </segment>
175
+ } }
176
+ it { expect(annotation.starts_after? 10).to be_falsey }
177
+ it { expect(annotation.starts_before? 10).to be true }
178
+ end
179
+ end
180
+ end