twitter-ads 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/CONTRIBUTING.md +77 -0
  5. data/LICENSE +22 -0
  6. data/README.md +111 -0
  7. data/Rakefile +86 -0
  8. data/bin/twitter-ads +42 -0
  9. data/lib/twitter-ads.rb +54 -0
  10. data/lib/twitter-ads/account.rb +229 -0
  11. data/lib/twitter-ads/audiences/tailored_audience.rb +177 -0
  12. data/lib/twitter-ads/campaign/app_list.rb +42 -0
  13. data/lib/twitter-ads/campaign/campaign.rb +40 -0
  14. data/lib/twitter-ads/campaign/funding_instrument.rb +33 -0
  15. data/lib/twitter-ads/campaign/line_item.rb +91 -0
  16. data/lib/twitter-ads/campaign/promotable_user.rb +28 -0
  17. data/lib/twitter-ads/campaign/targeting_criteria.rb +77 -0
  18. data/lib/twitter-ads/campaign/tweet.rb +83 -0
  19. data/lib/twitter-ads/client.rb +92 -0
  20. data/lib/twitter-ads/creative/app_download_card.rb +44 -0
  21. data/lib/twitter-ads/creative/image_app_download_card.rb +44 -0
  22. data/lib/twitter-ads/creative/image_conversation_card.rb +44 -0
  23. data/lib/twitter-ads/creative/lead_gen_card.rb +46 -0
  24. data/lib/twitter-ads/creative/promoted_account.rb +38 -0
  25. data/lib/twitter-ads/creative/promoted_tweet.rb +87 -0
  26. data/lib/twitter-ads/creative/video.rb +43 -0
  27. data/lib/twitter-ads/creative/video_app_download_card.rb +47 -0
  28. data/lib/twitter-ads/creative/video_conversation_card.rb +46 -0
  29. data/lib/twitter-ads/creative/website_card.rb +48 -0
  30. data/lib/twitter-ads/cursor.rb +127 -0
  31. data/lib/twitter-ads/enum.rb +135 -0
  32. data/lib/twitter-ads/error.rb +93 -0
  33. data/lib/twitter-ads/http/request.rb +127 -0
  34. data/lib/twitter-ads/http/response.rb +74 -0
  35. data/lib/twitter-ads/http/ton_upload.rb +140 -0
  36. data/lib/twitter-ads/legacy.rb +7 -0
  37. data/lib/twitter-ads/resources/analytics.rb +90 -0
  38. data/lib/twitter-ads/resources/dsl.rb +108 -0
  39. data/lib/twitter-ads/resources/persistence.rb +43 -0
  40. data/lib/twitter-ads/resources/resource.rb +92 -0
  41. data/lib/twitter-ads/targeting/reach_estimate.rb +69 -0
  42. data/lib/twitter-ads/utils.rb +76 -0
  43. data/lib/twitter-ads/version.rb +6 -0
  44. data/spec/fixtures/accounts_all.json +65 -0
  45. data/spec/fixtures/accounts_features.json +18 -0
  46. data/spec/fixtures/accounts_load.json +19 -0
  47. data/spec/fixtures/app_lists_all.json +22 -0
  48. data/spec/fixtures/app_lists_load.json +31 -0
  49. data/spec/fixtures/campaigns_all.json +208 -0
  50. data/spec/fixtures/campaigns_load.json +27 -0
  51. data/spec/fixtures/funding_instruments_all.json +74 -0
  52. data/spec/fixtures/funding_instruments_load.json +28 -0
  53. data/spec/fixtures/line_items_all.json +292 -0
  54. data/spec/fixtures/line_items_load.json +36 -0
  55. data/spec/fixtures/placements.json +35 -0
  56. data/spec/fixtures/promotable_users_all.json +57 -0
  57. data/spec/fixtures/promotable_users_load.json +18 -0
  58. data/spec/fixtures/promoted_tweets_all.json +212 -0
  59. data/spec/fixtures/promoted_tweets_load.json +19 -0
  60. data/spec/fixtures/reach_estimate.json +19 -0
  61. data/spec/fixtures/tailored_audiences_all.json +67 -0
  62. data/spec/fixtures/tailored_audiences_load.json +29 -0
  63. data/spec/fixtures/tweet_preview.json +24 -0
  64. data/spec/fixtures/videos_all.json +50 -0
  65. data/spec/fixtures/videos_load.json +22 -0
  66. data/spec/quality_spec.rb +15 -0
  67. data/spec/shared/properties.rb +20 -0
  68. data/spec/spec_helper.rb +61 -0
  69. data/spec/support/helpers.rb +42 -0
  70. data/spec/twitter-ads/account_spec.rb +315 -0
  71. data/spec/twitter-ads/audiences/tailored_audience_spec.rb +45 -0
  72. data/spec/twitter-ads/campaign/app_list_spec.rb +108 -0
  73. data/spec/twitter-ads/campaign/line_item_spec.rb +95 -0
  74. data/spec/twitter-ads/campaign/reach_estimate_spec.rb +98 -0
  75. data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +39 -0
  76. data/spec/twitter-ads/campaign/tweet_spec.rb +83 -0
  77. data/spec/twitter-ads/client_spec.rb +115 -0
  78. data/spec/twitter-ads/creative/app_download_card_spec.rb +44 -0
  79. data/spec/twitter-ads/creative/image_app_download_card_spec.rb +43 -0
  80. data/spec/twitter-ads/creative/image_conversation_card_spec.rb +40 -0
  81. data/spec/twitter-ads/creative/lead_gen_card_spec.rb +46 -0
  82. data/spec/twitter-ads/creative/promoted_account_spec.rb +30 -0
  83. data/spec/twitter-ads/creative/promoted_tweet_spec.rb +46 -0
  84. data/spec/twitter-ads/creative/video_app_download_card_spec.rb +42 -0
  85. data/spec/twitter-ads/creative/video_conversation_card_spec.rb +52 -0
  86. data/spec/twitter-ads/creative/video_legacy_spec.rb +43 -0
  87. data/spec/twitter-ads/creative/video_spec.rb +43 -0
  88. data/spec/twitter-ads/creative/website_card_spec.rb +37 -0
  89. data/spec/twitter-ads/cursor_spec.rb +67 -0
  90. data/spec/twitter-ads/placements_spec.rb +36 -0
  91. data/spec/twitter-ads/utils_spec.rb +101 -0
  92. data/twitter-ads.gemspec +37 -0
  93. metadata +247 -0
  94. metadata.gz.sig +0 -0
@@ -0,0 +1,29 @@
1
+ {
2
+ "data_type": "tailored_audiences",
3
+ "data": {
4
+ "targetable": false,
5
+ "name": "TA #2",
6
+ "targetable_types": [
7
+ "WEB",
8
+ "EXCLUDED_WEB"
9
+ ],
10
+ "audience_type": "WEB",
11
+ "id": "abc2",
12
+ "reasons_not_targetable": [
13
+ "TOO_SMALL"
14
+ ],
15
+ "list_type": null,
16
+ "created_at": "2014-03-09T20:35:41Z",
17
+ "updated_at": "2014-06-11T09:38:06Z",
18
+ "partner_source": "OTHER",
19
+ "deleted": false,
20
+ "audience_size": null
21
+ },
22
+ "request": {
23
+ "params": {
24
+ "account_id": "2iqph",
25
+ "name": "TA #2",
26
+ "list_type": "EMAIL"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "data_type": "tweet_preview",
3
+ "data": [
4
+ {
5
+ "platform": "web",
6
+ "preview": " <div><img><div><div><div><a>AdsAPI</a><a>@AdsAPI</a></div></div><div>Hello World!</div><div><div><img><div><div></div><div><span> Ratings/pricing not available for preview </span><span> Ratings/pricing not available for preview </span></div><div></div> INSTALL </div></div></div><a><img></a><div><div><span></span><span>Promoted by AdsAPI</span></div></div></div></div>"
7
+ },
8
+ {
9
+ "platform": "android",
10
+ "preview": " <div><img><div><div><div><a>AdsAPI</a><a>@AdsAPI</a></div></div><div>Hello World!</div><div><div><img><div><div></div><div><span> Ratings/pricing not available for preview </span><span> Ratings/pricing not available for preview </span></div><div></div> INSTALL </div></div></div><a><img></a><div><div><span></span><span>Promoted by AdsAPI</span></div></div></div></div>"
11
+ },
12
+ {
13
+ "platform": "iphone",
14
+ "preview": " <div><img><div><div><div><a>AdsAPI</a><a>@AdsAPI</a></div></div><div>Hello World!</div><div><div><img><div><div></div><div><span> Ratings/pricing not available for preview </span><span> Ratings/pricing not available for preview </span></div><div></div> INSTALL </div></div></div><a><img></a><div><div><span></span><span>Promoted by AdsAPI</span></div></div></div></div>"
15
+ }
16
+ ],
17
+ "request": {
18
+ "params": {
19
+ "status": "Hello World!",
20
+ "card_id": "pfs",
21
+ "account_id": "2iqph"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "data_type": "video",
3
+ "data": [
4
+ {
5
+ "tweeted": false,
6
+ "ready_to_tweet": true,
7
+ "duration": 289149,
8
+ "reasons_not_servable": [],
9
+ "description": "youtubz",
10
+ "preview_url": "https://amp.twimg.com/prod/multibr_v_1/video/2015/09/24/21/d5e3ae4d-34c4-4f49-894e-0c17735916a4-libx264-baseline-2528k.mp4?rYDB9GoYJcfWyizjXnBZra4UnGQMTkzUkIuW6OKjlXw%3D",
11
+ "id": "d5e3ae4d-34c4-4f49-894e-0c17735916a4",
12
+ "created_at": "2015-09-24T21:21:00Z",
13
+ "title": "video #1",
14
+ "updated_at": "2015-09-24T21:25:42Z",
15
+ "deleted": false
16
+ },
17
+ {
18
+ "tweeted": false,
19
+ "ready_to_tweet": true,
20
+ "duration": 16517,
21
+ "reasons_not_servable": [],
22
+ "description": "my favorite video",
23
+ "preview_url": "https://amp.twimg.com/prod/multibr_v_1/video/2015/09/24/21/a6d50810-c0bd-408e-b703-b789a82981ce-libx264-baseline-2528k.mp4?rYDB9GoYJcfWyizjXnBZra4UnGQMTkzUkIuW6OKjlXw%3D",
24
+ "id": "a6d50810-c0bd-408e-b703-b789a82981ce",
25
+ "created_at": "2015-09-24T21:11:01Z",
26
+ "title": "video #2",
27
+ "updated_at": "2015-09-24T21:11:41Z",
28
+ "deleted": false
29
+ },
30
+ {
31
+ "tweeted": false,
32
+ "ready_to_tweet": false,
33
+ "duration": 16517,
34
+ "reasons_not_servable": ["ERROR"],
35
+ "description": "another great video",
36
+ "preview_url": "https://amp.twimg.com/prod/multibr_v_1/video/2015/09/24/21/3beaeaca-0f2f-4aff-a025-e0d53a0aef2c-libx264-baseline-2528k.mp4?rYDB9GoYJcfWyizjXnBZra4UnGQMTkzUkIuW6OKjlXw%3D",
37
+ "id": "3beaeaca-0f2f-4aff-a025-e0d53a0aef2c",
38
+ "created_at": "2015-09-24T21:08:55Z",
39
+ "title": "video #3",
40
+ "updated_at": "2015-09-24T21:10:01Z",
41
+ "deleted": true
42
+ }
43
+ ],
44
+ "request": {
45
+ "params": {
46
+ "account_id": "2iqph"
47
+ }
48
+ },
49
+ "next_cursor": null
50
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "data_type": "video",
3
+ "data": {
4
+ "tweeted": false,
5
+ "ready_to_tweet": true,
6
+ "duration": 289149,
7
+ "reasons_not_servable": [],
8
+ "description": "youtubz",
9
+ "preview_url": "https://amp.twimg.com/prod/multibr_v_1/video/2015/09/24/21/d5e3ae4d-34c4-4f49-894e-0c17735916a4-libx264-baseline-2528k.mp4?rYDB9GoYJcfWyizjXnBZra4UnGQMTkzUkIuW6OKjlXw%3D",
10
+ "id": "d5e3ae4d-34c4-4f49-894e-0c17735916a4",
11
+ "created_at": "2015-09-24T21:21:00Z",
12
+ "title": "video #1",
13
+ "updated_at": "2015-09-24T21:25:42Z",
14
+ "deleted": false
15
+ },
16
+ "request": {
17
+ "params": {
18
+ "video_id": "d5e3ae4d-34c4-4f49-894e-0c17735916a4",
19
+ "account_id": "2iqph"
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe 'Code Style & Quality', :quality do
7
+
8
+ # If this test fails, you need to run 'rubocop' to see what needs to be fixed.
9
+ # Please see the CONTRIBUTING.md file for additional style guide information.
10
+ it 'has no style guide or quality violations', :style do
11
+ result = silence { RuboCop::CLI.new.run([]) }
12
+ expect(result).to eq(0)
13
+ end unless RUBY_ENGINE =~ /rbx/
14
+
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ RSpec.shared_examples 'object property check' do |read, write|
5
+
6
+ it 'has all the correct write properties' do
7
+ write.each do |prop|
8
+ expect(subject.respond_to?(prop)).to be true
9
+ expect(subject.respond_to?("#{prop}=")).to be true
10
+ end
11
+ end
12
+
13
+ it 'has all the correct read-only properties' do
14
+ read.each do |prop|
15
+ expect(subject.respond_to?(prop)).to be true
16
+ expect(subject.respond_to?("#{prop}=")).not_to be true
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ unless RUBY_PLATFORM =~ /java/ || RUBY_ENGINE =~ /rbx/
5
+ require 'simplecov'
6
+ require 'codeclimate-test-reporter'
7
+
8
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ CodeClimate::TestReporter::Formatter
11
+ ]
12
+
13
+ SimpleCov.start do
14
+ add_filter '/spec/'
15
+ minimum_coverage(80.00)
16
+ end
17
+ end
18
+
19
+ require 'rubocop'
20
+ require 'faker'
21
+
22
+ require 'webmock/rspec'
23
+ WebMock.disable_net_connect!(allow: 'codeclimate.com')
24
+
25
+ require 'twitter-ads'
26
+ include TwitterAds
27
+
28
+ # Require All Support Files
29
+ Dir['./spec/support/*.rb'].sort.each { |file| require file }
30
+
31
+ ADS_API = 'https://ads-api.twitter.com/0'.freeze
32
+ TON_API = 'https://ton.twitter.com'.freeze
33
+ UPLOAD_API = 'https://upload.twitter.com/1.1'.freeze
34
+
35
+ RSpec.configure do |config|
36
+ # Helpers & Custom Matchers
37
+ include Helpers
38
+
39
+ # General
40
+ config.color = true
41
+ config.fail_fast = true unless ENV['CI']
42
+ config.formatter = ENV['CI'] ? 'documentation' : 'progress'
43
+ config.profile_examples = 5
44
+
45
+ # Expectations
46
+ config.expect_with :rspec do |expectations|
47
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
48
+ end
49
+
50
+ # Mocking
51
+ config.mock_with :rspec do |mocks|
52
+ mocks.verify_partial_doubles = true
53
+ end
54
+
55
+ # Test Order & Filtering
56
+ config.order = :random
57
+ Kernel.srand config.seed
58
+ end
59
+
60
+ # Require All Shared Examples
61
+ Dir['./spec/shared/*.rb'].sort.each { |file| require file }
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ # Helper module for custom test matchers and utilities.
5
+ module Helpers
6
+
7
+ # Helper method to temporarily redirect stdout and stderr for
8
+ # a given block of code.
9
+ #
10
+ # @param &block [Block] The code block to be executed.
11
+ #
12
+ # @return [Object] The result of the block.
13
+ def silence(&block)
14
+ original_stdout = $stdout
15
+ original_stderr = $stderr
16
+ $stdout = $stderr = File.new('/dev/null', 'w')
17
+ yield block
18
+ ensure
19
+ $stdout = original_stdout
20
+ $stderr = original_stderr
21
+ end
22
+
23
+ # Helper method for returning a JSON fixture file.
24
+ #
25
+ # @param name [Symbol] The name of the fixture file to be returned.
26
+ #
27
+ # @return [File] The file instance of the fixture specified.
28
+ def fixture(name)
29
+ File.new(File.expand_path("../../fixtures/#{name}.json", __FILE__))
30
+ end
31
+
32
+ # Helper method to stub a request with a fixture by name.
33
+ #
34
+ # @param method [Symbol] The HTTP method (:get, :put, :post, :delete)
35
+ # @param name [String] The fixture name.
36
+ # @param url [String] The URL match pattern or regular expression.
37
+ # @param status [Integer] The response code to return with the fixture.
38
+ def stub_fixture(method, name, url, status = 200)
39
+ stub_request(method, url).to_return(body: fixture(name), status: status)
40
+ end
41
+
42
+ end
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe TwitterAds::Account do
7
+
8
+ before(:each) do
9
+ stub_fixture(:get, :accounts_all, "#{ADS_API}/accounts")
10
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
11
+ end
12
+
13
+ let(:client) do
14
+ Client.new(
15
+ Faker::Lorem.characters(15),
16
+ Faker::Lorem.characters(40),
17
+ "123456-#{Faker::Lorem.characters(40)}",
18
+ Faker::Lorem.characters(40)
19
+ )
20
+ end
21
+
22
+ let(:account) { client.accounts.first }
23
+
24
+ describe '#initialize' do
25
+
26
+ let(:account) { TwitterAds::Account.new(client) }
27
+ let(:methods) {
28
+ %w(features promotable_users funding_instruments campaigns line_items)
29
+ }
30
+
31
+ it 'raises an ArgumentError error if used without initialization' do
32
+ expect(account).to receive(:validate_loaded).and_call_original.exactly(methods.size).times
33
+ methods.each { |meth| expect { account.send(meth) }.to raise_error(ArgumentError) }
34
+ end
35
+
36
+ end
37
+
38
+ describe '#inspect' do
39
+
40
+ it 'includes the object id value' do
41
+ expect(account.inspect).to include(account.object_id.to_s)
42
+ end
43
+
44
+ it 'includes the class.name value' do
45
+ expect(account.inspect).to include(account.class.name)
46
+ end
47
+
48
+ it 'includes the Account ID value' do
49
+ expect(account.inspect).to include(account.id)
50
+ end
51
+
52
+ end
53
+
54
+ describe '#features' do
55
+
56
+ before(:each) do
57
+ stub_fixture(:get, :accounts_features, "#{ADS_API}/accounts/#{account.id}/features")
58
+ end
59
+
60
+ it 'successfully returns a list of features available' do
61
+ features = account.features
62
+ expect(features).not_to be_nil
63
+ expect(features).to all(be_a(String))
64
+ end
65
+
66
+ end
67
+
68
+ describe '#promotable_users' do
69
+
70
+ before(:each) do
71
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/promotable_users"
72
+ stub_fixture(:get, :promotable_users_all, resource_collection)
73
+
74
+ resource = "#{ADS_API}/accounts/#{account.id}/promotable_users/4k"
75
+ stub_fixture(:get, :promotable_users_load, /#{resource}\?.*/)
76
+ end
77
+
78
+ context 'with an id specified' do
79
+
80
+ it 'succesfully loads the specified promoted tweet' do
81
+ result = account.promotable_users('4k')
82
+ expect(result).not_to be_nil
83
+ expect(result.class).to eq(TwitterAds::PromotableUser)
84
+ expect(result.id).to eq('4k')
85
+ end
86
+
87
+ end
88
+
89
+ context 'without an id specified' do
90
+
91
+ it 'succesfully returns a Cursor with all promoted tweets' do
92
+ result = account.promotable_users
93
+ expect(result.size).to eq(5)
94
+ expect(result.class).to eq(TwitterAds::Cursor)
95
+ expect(result.first.class).to eq(TwitterAds::PromotableUser)
96
+ expect(result.first.id).to eq('4k')
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+
103
+ describe '#funding_instruments' do
104
+
105
+ before(:each) do
106
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/funding_instruments"
107
+ stub_fixture(:get, :funding_instruments_all, resource_collection)
108
+
109
+ resource = "#{ADS_API}/accounts/#{account.id}/funding_instruments/5aa0p"
110
+ stub_fixture(:get, :funding_instruments_load, /#{resource}\?.*/)
111
+ end
112
+
113
+ context 'with an id specified' do
114
+
115
+ it 'succesfully loads the specified promoted tweet' do
116
+ result = account.funding_instruments('5aa0p')
117
+ expect(result).not_to be_nil
118
+ expect(result.class).to eq(TwitterAds::FundingInstrument)
119
+ expect(result.id).to eq('5aa0p')
120
+ end
121
+
122
+ end
123
+
124
+ context 'without an id specified' do
125
+
126
+ it 'succesfully returns a Cursor with all promoted tweets' do
127
+ result = account.funding_instruments
128
+ expect(result.size).to eq(3)
129
+ expect(result.class).to eq(TwitterAds::Cursor)
130
+ expect(result.first.class).to eq(TwitterAds::FundingInstrument)
131
+ expect(result.first.id).to eq('5aa0p')
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ describe '#campaigns' do
139
+
140
+ before(:each) do
141
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/campaigns"
142
+ stub_fixture(:get, :campaigns_all, resource_collection)
143
+
144
+ resource = "#{ADS_API}/accounts/#{account.id}/campaigns/2wap7"
145
+ stub_fixture(:get, :campaigns_load, /#{resource}\?.*/)
146
+ end
147
+
148
+ context 'with an id specified' do
149
+
150
+ it 'succesfully loads the specified promoted tweet' do
151
+ result = account.campaigns('2wap7')
152
+ expect(result).not_to be_nil
153
+ expect(result.class).to eq(TwitterAds::Campaign)
154
+ expect(result.id).to eq('2wap7')
155
+ end
156
+
157
+ end
158
+
159
+ context 'without an id specified' do
160
+
161
+ it 'succesfully returns a Cursor with all promoted tweets' do
162
+ result = account.campaigns
163
+ expect(result.size).to eq(10)
164
+ expect(result.class).to eq(TwitterAds::Cursor)
165
+ expect(result.first.class).to eq(TwitterAds::Campaign)
166
+ expect(result.first.id).to eq('2wap7')
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ describe '#line_items' do
174
+
175
+ before(:each) do
176
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/line_items"
177
+ stub_fixture(:get, :line_items_all, resource_collection)
178
+
179
+ resource = "#{ADS_API}/accounts/#{account.id}/line_items/bw2"
180
+ stub_fixture(:get, :line_items_load, /#{resource}\?.*/)
181
+ end
182
+
183
+ context 'with an id specified' do
184
+
185
+ it 'succesfully loads the specified promoted tweet' do
186
+ result = account.line_items('bw2')
187
+ expect(result).not_to be_nil
188
+ expect(result.class).to eq(TwitterAds::LineItem)
189
+ expect(result.id).to eq('bw2')
190
+ end
191
+
192
+ end
193
+
194
+ context 'without an id specified' do
195
+
196
+ it 'succesfully returns a Cursor with all promoted tweets' do
197
+ result = account.line_items
198
+ expect(result.size).to eq(10)
199
+ expect(result.class).to eq(TwitterAds::Cursor)
200
+ expect(result.first.class).to eq(TwitterAds::LineItem)
201
+ expect(result.first.id).to eq('bw2')
202
+ end
203
+
204
+ end
205
+
206
+ end
207
+
208
+ describe '#app_lists' do
209
+
210
+ before(:each) do
211
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/app_lists"
212
+ stub_fixture(:get, :app_lists_all, resource_collection)
213
+
214
+ resource = "#{ADS_API}/accounts/#{account.id}/app_lists/abc2"
215
+ stub_fixture(:get, :app_lists_load, /#{resource}\?.*/)
216
+ end
217
+
218
+ context 'with an id specified' do
219
+
220
+ it 'successfully loads the specified app list' do
221
+ result = account.app_lists('abc2')
222
+ expect(result).not_to be_nil
223
+ expect(result.class).to eq(TwitterAds::AppList)
224
+ expect(result.id).to eq('abc2')
225
+ end
226
+
227
+ end
228
+
229
+ context 'without an id specified' do
230
+
231
+ it 'succesfully returns a cursor with all app lists' do
232
+ result = account.app_lists
233
+ expect(result.to_a.size).to eq(3)
234
+ expect(result.class).to eq(TwitterAds::Cursor)
235
+ expect(result.first.class).to eq(TwitterAds::AppList)
236
+ expect(result.first.id).to eq('abc2')
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ describe '#tailored_audiences' do
244
+
245
+ before(:each) do
246
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/tailored_audiences"
247
+ stub_fixture(:get, :tailored_audiences_all, resource_collection)
248
+
249
+ resource = "#{ADS_API}/accounts/#{account.id}/tailored_audiences/abc2"
250
+ stub_fixture(:get, :tailored_audiences_load, /#{resource}\?.*/)
251
+ end
252
+
253
+ context 'with an id specified' do
254
+
255
+ it 'successfully loads the specified tailored audience' do
256
+ result = account.tailored_audiences('abc2')
257
+ expect(result).not_to be_nil
258
+ expect(result.class).to eq(TwitterAds::TailoredAudience)
259
+ expect(result.id).to eq('abc2')
260
+ end
261
+
262
+ end
263
+
264
+ context 'without an id specified' do
265
+
266
+ it 'succesfully returns a cursor with all tailored audiences' do
267
+ result = account.tailored_audiences
268
+ expect(result.to_a.size).to eq(3)
269
+ expect(result.class).to eq(TwitterAds::Cursor)
270
+ expect(result.first.class).to eq(TwitterAds::TailoredAudience)
271
+ expect(result.first.id).to eq('abc2')
272
+ end
273
+
274
+ end
275
+
276
+ end
277
+
278
+ describe '#videos' do
279
+
280
+ let!(:video_id) { 'd5e3ae4d-34c4-4f49-894e-0c17735916a4' }
281
+
282
+ before(:each) do
283
+ resource_collection = "#{ADS_API}/accounts/#{account.id}/videos"
284
+ stub_fixture(:get, :videos_all, resource_collection)
285
+
286
+ resource = "#{ADS_API}/accounts/#{account.id}/videos/#{video_id}"
287
+ stub_fixture(:get, :videos_load, /#{resource}\?.*/)
288
+ end
289
+
290
+ context 'with an id specified' do
291
+
292
+ it 'successfully loads the specified video' do
293
+ result = account.videos(video_id)
294
+ expect(result).not_to be_nil
295
+ expect(result.class).to eq(TwitterAds::Video)
296
+ expect(result.id).to eq(video_id)
297
+ end
298
+
299
+ end
300
+
301
+ context 'without an id specified' do
302
+
303
+ it 'succesfully returns a cursor with all videos' do
304
+ result = account.videos
305
+ expect(result.to_a.size).to eq(3)
306
+ expect(result.class).to eq(TwitterAds::Cursor)
307
+ expect(result.first.class).to eq(TwitterAds::Video)
308
+ expect(result.first.id).to eq(video_id)
309
+ end
310
+
311
+ end
312
+
313
+ end
314
+
315
+ end