twitter-ads 5.1.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -2
  3. data/README.md +1 -1
  4. data/lib/twitter-ads.rb +8 -4
  5. data/lib/twitter-ads/account.rb +6 -25
  6. data/lib/twitter-ads/audiences/tailored_audience.rb +98 -3
  7. data/lib/twitter-ads/{targeting_criteria/behavior_taxonomy.rb → campaign/advertiser_business_categories.rb} +6 -6
  8. data/lib/twitter-ads/campaign/campaign.rb +1 -2
  9. data/lib/twitter-ads/campaign/content_categories.rb +23 -0
  10. data/lib/twitter-ads/campaign/funding_instrument.rb +1 -2
  11. data/lib/twitter-ads/campaign/line_item.rb +4 -4
  12. data/lib/twitter-ads/campaign/organic_tweet.rb +1 -3
  13. data/lib/twitter-ads/campaign/targeting_criteria.rb +0 -1
  14. data/lib/twitter-ads/campaign/tweet.rb +4 -49
  15. data/lib/twitter-ads/client.rb +2 -2
  16. data/lib/twitter-ads/creative/account_media.rb +4 -6
  17. data/lib/twitter-ads/creative/draft_tweet.rb +40 -0
  18. data/lib/twitter-ads/creative/image_app_download_card.rb +2 -2
  19. data/lib/twitter-ads/creative/image_conversation_card.rb +3 -2
  20. data/lib/twitter-ads/creative/media_creative.rb +2 -3
  21. data/lib/twitter-ads/creative/media_library.rb +12 -13
  22. data/lib/twitter-ads/creative/promoted_account.rb +1 -2
  23. data/lib/twitter-ads/creative/promoted_tweet.rb +1 -2
  24. data/lib/twitter-ads/creative/scheduled_tweet.rb +1 -12
  25. data/lib/twitter-ads/creative/tweets.rb +52 -0
  26. data/lib/twitter-ads/creative/video_app_download_card.rb +4 -6
  27. data/lib/twitter-ads/creative/video_conversation_card.rb +6 -6
  28. data/lib/twitter-ads/creative/video_website_card.rb +3 -5
  29. data/lib/twitter-ads/creative/website_card.rb +2 -2
  30. data/lib/twitter-ads/cursor.rb +6 -0
  31. data/lib/twitter-ads/enum.rb +19 -9
  32. data/lib/twitter-ads/error.rb +5 -15
  33. data/lib/twitter-ads/http/request.rb +37 -2
  34. data/lib/twitter-ads/http/response.rb +1 -13
  35. data/lib/twitter-ads/resources/analytics.rb +99 -47
  36. data/lib/twitter-ads/resources/dsl.rb +8 -1
  37. data/lib/twitter-ads/restapi.rb +29 -0
  38. data/lib/twitter-ads/settings/tax.rb +13 -1
  39. data/lib/twitter-ads/targeting/audience_summary.rb +47 -0
  40. data/lib/twitter-ads/targeting_criteria/{behavior.rb → conversation.rb} +3 -7
  41. data/lib/twitter-ads/utils.rb +23 -0
  42. data/lib/twitter-ads/version.rb +1 -1
  43. data/spec/fixtures/audience_summary.json +14 -0
  44. data/spec/fixtures/line_items_all.json +2 -10
  45. data/spec/fixtures/line_items_load.json +0 -1
  46. data/spec/fixtures/tweet_previews.json +23 -0
  47. data/spec/spec_helper.rb +1 -4
  48. data/spec/twitter-ads/campaign/line_item_spec.rb +0 -1
  49. data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +0 -1
  50. data/spec/twitter-ads/campaign/tweet_spec.rb +0 -59
  51. data/spec/twitter-ads/client_spec.rb +17 -1
  52. data/spec/twitter-ads/creative/media_creative_spec.rb +1 -1
  53. data/spec/twitter-ads/creative/tweet_previews_spec.rb +41 -0
  54. data/spec/twitter-ads/rate_limit_spec.rb +247 -0
  55. data/spec/twitter-ads/retry_count_spec.rb +61 -0
  56. data/spec/twitter-ads/{creative/image_app_download_card_spec.rb → targeting/audience_summary_spec.rb} +16 -18
  57. metadata +46 -47
  58. data/lib/twitter-ads/audiences/audience_intelligence.rb +0 -68
  59. data/lib/twitter-ads/targeting/reach_estimate.rb +0 -78
  60. data/spec/fixtures/tweet_preview.json +0 -24
  61. data/spec/twitter-ads/campaign/reach_estimate_spec.rb +0 -103
  62. data/spec/twitter-ads/creative/account_media_spec.rb +0 -32
  63. data/spec/twitter-ads/creative/image_conversation_card_spec.rb +0 -40
  64. data/spec/twitter-ads/creative/video_app_download_card_spec.rb +0 -42
  65. data/spec/twitter-ads/creative/video_conversation_card_spec.rb +0 -51
  66. data/spec/twitter-ads/creative/website_card_spec.rb +0 -42
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ module AudienceSummary
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+
10
+ RESOURCE = "/#{TwitterAds::API_VERSION}/" \
11
+ 'accounts/%{account_id}/audience_summary'
12
+
13
+ property :audience_size, read_only: true
14
+
15
+ class << self
16
+
17
+ # Get an audience summary for the specified targeting criteria.
18
+ #
19
+ # @example
20
+ # TwitterAds::AudienceSummary.fetch(
21
+ # account,
22
+ # params: {targeting_criteria:[{targeting_type:'LOCATION',
23
+ # targeting_value:'96683cc9126741d1'}]}
24
+ # )
25
+ #
26
+ # @param params [Hash] A hash of input targeting criteria values
27
+ #
28
+ # @return [Hash] A hash containing the min and max audience size.
29
+ #
30
+ # @since 7.0.0
31
+ # @see https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/audience-summary
32
+ def fetch(account, params)
33
+ resource = RESOURCE % { account_id: account.id }
34
+ headers = { 'Content-Type' => 'application/json' }
35
+
36
+ response = TwitterAds::Request.new(account.client,
37
+ :post,
38
+ resource,
39
+ headers: headers,
40
+ body: params.to_json).perform
41
+ response.body[:data]
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -2,22 +2,18 @@
2
2
  # Copyright (C) 2019 Twitter, Inc.
3
3
 
4
4
  module TwitterAds
5
- class Behavior
5
+ class Conversation
6
6
 
7
7
  include TwitterAds::DSL
8
8
  include TwitterAds::Resource
9
9
 
10
- property :id, read_only: true
11
10
  property :name, read_only: true
12
11
  property :targeting_type, read_only: true
13
12
  property :targeting_value, read_only: true
14
- property :audience_code, read_only: true
15
- property :country_code, read_only: true
16
- property :partner_source, read_only: true
17
- property :targetable_type, read_only: true
13
+ property :conversation_type, read_only: true
18
14
 
19
15
  RESOURCE_COLLECTION = "/#{TwitterAds::API_VERSION}/" \
20
- 'targeting_criteria/behaviors' # @api private
16
+ 'targeting_criteria/conversations' # @api private
21
17
 
22
18
  def initialize(account)
23
19
  @account = account
@@ -70,6 +70,29 @@ module TwitterAds
70
70
  warn message
71
71
  end
72
72
 
73
+ def extract_response_headers(headers)
74
+ values = {}
75
+ # only get "X-${name}" custom response headers
76
+ headers.each { |key, value|
77
+ if key =~ /^x-/
78
+ values[key.gsub(/^x-/, '').tr('-', '_')] = \
79
+ value.first =~ /^[0-9]*$/ ? value.first.to_i : value.first
80
+ end
81
+ }
82
+ values
83
+ end
84
+
85
+ def flatten_params(args)
86
+ params = args
87
+ params.each { |key, value|
88
+ if value.is_a?(Array)
89
+ next if value.empty?
90
+ params[key] = value.join(',')
91
+ end
92
+ }
93
+ params
94
+ end
95
+
73
96
  end
74
97
 
75
98
  end
@@ -2,5 +2,5 @@
2
2
  # Copyright (C) 2019 Twitter, Inc.
3
3
 
4
4
  module TwitterAds
5
- VERSION = '5.1.0'
5
+ VERSION = '7.0.1'
6
6
  end
@@ -0,0 +1,14 @@
1
+ {
2
+ "request": {
3
+ "params": {
4
+ "targeting_criteria": null,
5
+ "account_id": "2iqph"
6
+ }
7
+ },
8
+ "data": {
9
+ "audience_size": {
10
+ "min": 41133600,
11
+ "max": 50274400
12
+ }
13
+ }
14
+ }
@@ -29,7 +29,6 @@
29
29
  "currency": "USD",
30
30
  "created_at": "2011-07-11T20:36:11Z",
31
31
  "updated_at": "2011-09-04T19:39:51Z",
32
- "include_sentiment": null,
33
32
  "campaign_id": "2wap7",
34
33
  "deleted": false
35
34
  },
@@ -57,7 +56,6 @@
57
56
  "currency": "USD",
58
57
  "created_at": "2011-07-13T20:56:39Z",
59
58
  "updated_at": "2011-09-04T19:39:04Z",
60
- "include_sentiment": null,
61
59
  "campaign_id": "2wamv",
62
60
  "deleted": false
63
61
  },
@@ -85,7 +83,6 @@
85
83
  "currency": "USD",
86
84
  "created_at": "2011-07-14T00:04:47Z",
87
85
  "updated_at": "2011-09-04T19:39:39Z",
88
- "include_sentiment": null,
89
86
  "campaign_id": "2wai9",
90
87
  "deleted": false
91
88
  },
@@ -113,7 +110,6 @@
113
110
  "currency": "USD",
114
111
  "created_at": "2011-08-22T22:42:18Z",
115
112
  "updated_at": "2011-09-04T19:40:02Z",
116
- "include_sentiment": null,
117
113
  "campaign_id": "2of1n",
118
114
  "deleted": false
119
115
  },
@@ -141,7 +137,7 @@
141
137
  "currency": "JPY",
142
138
  "created_at": "2011-08-26T20:51:14Z",
143
139
  "updated_at": "2011-08-26T21:30:25Z",
144
- "include_sentiment": "POSITIVE_ONLY",
140
+
145
141
  "campaign_id": "2w9n1",
146
142
  "deleted": true
147
143
  },
@@ -169,7 +165,7 @@
169
165
  "currency": "USD",
170
166
  "created_at": "2011-08-26T21:38:51Z",
171
167
  "updated_at": "2011-08-26T22:24:37Z",
172
- "include_sentiment": "POSITIVE_ONLY",
168
+
173
169
  "campaign_id": "2vuug",
174
170
  "deleted": true
175
171
  },
@@ -197,7 +193,6 @@
197
193
  "currency": "JPY",
198
194
  "created_at": "2011-08-26T22:28:55Z",
199
195
  "updated_at": "2011-09-04T19:38:46Z",
200
- "include_sentiment": null,
201
196
  "campaign_id": "2vuj3",
202
197
  "deleted": false
203
198
  },
@@ -225,7 +220,6 @@
225
220
  "currency": "USD",
226
221
  "created_at": "2011-09-01T17:25:04Z",
227
222
  "updated_at": "2011-09-16T02:56:55Z",
228
- "include_sentiment": null,
229
223
  "campaign_id": "2v3c4",
230
224
  "deleted": true
231
225
  },
@@ -253,7 +247,6 @@
253
247
  "currency": "JPY",
254
248
  "created_at": "2011-09-06T17:42:52Z",
255
249
  "updated_at": "2011-09-30T18:54:18Z",
256
- "include_sentiment": null,
257
250
  "campaign_id": "2ttv3",
258
251
  "deleted": false
259
252
  },
@@ -281,7 +274,6 @@
281
274
  "currency": "USD",
282
275
  "created_at": "2011-09-06T22:44:13Z",
283
276
  "updated_at": "2011-09-20T01:32:27Z",
284
- "include_sentiment": null,
285
277
  "campaign_id": "2ttv3",
286
278
  "deleted": true
287
279
  }
@@ -24,7 +24,6 @@
24
24
  "currency": "USD",
25
25
  "created_at": "2011-07-11T20:36:11Z",
26
26
  "updated_at": "2011-09-04T19:39:51Z",
27
- "include_sentiment": null,
28
27
  "campaign_id": "2wap7",
29
28
  "deleted": false
30
29
  },
@@ -0,0 +1,23 @@
1
+ {
2
+ "data_type": "tweet_previews",
3
+ "request": {
4
+ "params": {
5
+ "tweet_ids": [
6
+ "1130942781109596160",
7
+ "1101254234031370240"
8
+ ],
9
+ "tweet_type": "PUBLISHED",
10
+ "account_id": "2iqph"
11
+ }
12
+ },
13
+ "data": [
14
+ {
15
+ "tweet_id": "1130942781109596160",
16
+ "preview": "<iframe class='tweet-preview' src='https://ton.smf1.twitter.com/ads-manager/tweet-preview/index.html?data=c29tZSByYW5kb20gYmFzZTY0IHN0cmluZ3MgaGVyZS4uLg=='>"
17
+ },
18
+ {
19
+ "tweet_id": "1101254234031370240",
20
+ "preview": "<iframe class='tweet-preview' src='https://ton.smf1.twitter.com/ads-manager/tweet-preview/index.html?data=c29tZSByYW5kb20gYmFzZTY0IHN0cmluZ3MgaGVyZS4uLg=='>"
21
+ }
22
+ ]
23
+ }
@@ -3,12 +3,10 @@
3
3
 
4
4
  unless RUBY_PLATFORM =~ /java/ || RUBY_ENGINE =~ /rbx/
5
5
  require 'simplecov'
6
- require 'codeclimate-test-reporter'
7
6
 
8
7
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
9
8
  [
10
- SimpleCov::Formatter::HTMLFormatter,
11
- CodeClimate::TestReporter::Formatter
9
+ SimpleCov::Formatter::HTMLFormatter
12
10
  ]
13
11
  )
14
12
 
@@ -22,7 +20,6 @@ require 'rubocop'
22
20
  require 'faker'
23
21
 
24
22
  require 'webmock/rspec'
25
- WebMock.disable_net_connect!(allow: 'codeclimate.com')
26
23
 
27
24
  require 'twitter-ads'
28
25
  include TwitterAds
@@ -32,7 +32,6 @@ describe TwitterAds::LineItem do
32
32
  advertiser_domain
33
33
  categories
34
34
  charge_by
35
- include_sentiment
36
35
  objective
37
36
  entity_status
38
37
  primary_web_event_tag
@@ -31,7 +31,6 @@ describe TwitterAds::TargetingCriteria do
31
31
  targeting_type
32
32
  targeting_value
33
33
  tailored_audience_expansion
34
- tailored_audience_type
35
34
  )
36
35
 
37
36
  include_examples 'object property check', read, write
@@ -21,63 +21,4 @@ describe TwitterAds::Tweet do
21
21
 
22
22
  let(:account) { client.accounts.first }
23
23
 
24
- describe '#tweet_preview' do
25
-
26
- let!(:resource_collection) { "#{ADS_API}/accounts/#{account.id}/tweet/preview" }
27
-
28
- before(:each) do
29
- stub_fixture(:get, :tweet_preview, /#{resource_collection}.*/)
30
- end
31
-
32
- context 'with an existing tweet id' do
33
-
34
- it 'successfully returns a preview of the specified tweet' do
35
- params = { id: 634798319504617472 }
36
- result = subject.preview(account, params)
37
- expect(result.size).not_to be_nil
38
- expect(result).to all(include(:platform, :preview))
39
- end
40
-
41
- end
42
-
43
- context 'when previewing a new tweet' do
44
-
45
- it 'url encodes the status content' do
46
- params = { text: 'Hello World!', card_id: '19v69' }
47
- expect(URI).to receive(:escape).at_least(:once).and_call_original
48
- result = subject.preview(account, params)
49
- expect(result.size).not_to be_nil
50
- expect(result).to all(include(:platform, :preview))
51
- end
52
-
53
- it 'allows a single value for the media_ids param' do
54
- resource = "/#{TwitterAds::API_VERSION}/accounts/#{account.id}/tweet/preview"
55
- expected = { text: 'Hello%20World!', media_ids: 634458428836962304 }
56
-
57
- expect(TwitterAds::Request).to receive(:new).with(
58
- account.client, :get, resource, params: expected).and_call_original
59
-
60
- params = { text: 'Hello World!', media_ids: 634458428836962304 }
61
- result = subject.preview(account, params)
62
- expect(result.size).not_to be_nil
63
- expect(result).to all(include(:platform, :preview))
64
- end
65
-
66
- it 'allows an array of values for the media_ids param' do
67
- resource = "/#{TwitterAds::API_VERSION}/accounts/#{account.id}/tweet/preview"
68
- expected = { text: 'Hello%20World!', media_ids: '634458428836962304,634458428836962305' }
69
-
70
- expect(TwitterAds::Request).to receive(:new).with(
71
- account.client, :get, resource, params: expected).and_call_original
72
-
73
- params = { text: 'Hello World!', media_ids: [634458428836962304, 634458428836962305] }
74
- result = subject.preview(account, params)
75
- expect(result.size).not_to be_nil
76
- expect(result).to all(include(:platform, :preview))
77
- end
78
-
79
- end
80
-
81
- end
82
-
83
24
  end
@@ -55,10 +55,26 @@ describe TwitterAds::Client do
55
55
 
56
56
  it 'allows additional options' do
57
57
  expect {
58
- Client.new(consumer_key, consumer_secret, access_token, {})
58
+ Client.new(consumer_key, consumer_secret, access_token, access_token_secret, options: {})
59
59
  }.not_to raise_error
60
60
  end
61
61
 
62
+ it 'test client options' do
63
+ client = Client.new(
64
+ consumer_key,
65
+ consumer_secret,
66
+ access_token,
67
+ access_token_secret,
68
+ options: {
69
+ handle_rate_limit: true,
70
+ retry_max: 1,
71
+ retry_delay: 3000,
72
+ retry_on_status: [404, 500, 503]
73
+ }
74
+ )
75
+ expect(client.options.length).to eq 4
76
+ end
77
+
62
78
  end
63
79
 
64
80
  describe '#inspect' do
@@ -25,7 +25,7 @@ describe TwitterAds::Creative::MediaCreative do
25
25
 
26
26
  # check model properties
27
27
  subject { described_class.new(account) }
28
- read = %w(id created_at updated_at deleted approval_status serving_status)
28
+ read = %w(id created_at updated_at deleted approval_status entity_status)
29
29
  write = %w(account_media_id line_item_id landing_url)
30
30
  include_examples 'object property check', read, write
31
31
 
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ include TwitterAds::Enum
7
+
8
+ describe TwitterAds::Creative::TweetPreview do
9
+
10
+ let!(:resource) { "#{ADS_API}/accounts/2iqph/tweet_previews" }
11
+
12
+ before(:each) do
13
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
14
+ stub_fixture(:get, :tweet_previews, /#{resource}\?.*/)
15
+ end
16
+
17
+ let(:client) do
18
+ Client.new(
19
+ Faker::Lorem.characters(40),
20
+ Faker::Lorem.characters(40),
21
+ Faker::Lorem.characters(40),
22
+ Faker::Lorem.characters(40)
23
+ )
24
+ end
25
+
26
+ let(:account) { client.accounts('2iqph') }
27
+ let(:instance) { described_class.new(account) }
28
+
29
+ it 'inspect TweetPreview.load() response' do
30
+ preview = instance.load(
31
+ account,
32
+ tweet_ids: %w(1130942781109596160 1101254234031370240),
33
+ tweet_type: TweetType::PUBLISHED)
34
+
35
+ expect(preview).to be_instance_of(Cursor)
36
+ expect(preview.count).to eq 2
37
+ tweet = preview.first
38
+ expect(tweet.tweet_id).to eq '1130942781109596160'
39
+ end
40
+
41
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe TwitterAds::Campaign do
7
+
8
+ before(:each) do
9
+ allow_any_instance_of(Object).to receive(:sleep)
10
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
11
+ end
12
+
13
+ let(:client) do
14
+ Client.new(
15
+ Faker::Lorem.characters(40),
16
+ Faker::Lorem.characters(40),
17
+ Faker::Lorem.characters(40),
18
+ Faker::Lorem.characters(40)
19
+ )
20
+ end
21
+ let(:account) { client_simple.accounts('2iqph') }
22
+
23
+ let(:client_simple) do
24
+ Client.new(
25
+ Faker::Lorem.characters(40),
26
+ Faker::Lorem.characters(40),
27
+ Faker::Lorem.characters(40),
28
+ Faker::Lorem.characters(40),
29
+ options: {
30
+ handle_rate_limit: true
31
+ }
32
+ )
33
+ end
34
+ let(:account1) { client_simple.accounts('2iqph') }
35
+
36
+ let(:client_combine) do
37
+ Client.new(
38
+ Faker::Lorem.characters(40),
39
+ Faker::Lorem.characters(40),
40
+ Faker::Lorem.characters(40),
41
+ Faker::Lorem.characters(40),
42
+ options: {
43
+ handle_rate_limit: true,
44
+ retry_max: 1,
45
+ retry_delay: 3000,
46
+ retry_on_status: [500]
47
+ }
48
+ )
49
+ end
50
+ let(:account2) { client_combine.accounts('2iqph') }
51
+
52
+ it 'test rate-limit handle - success' do
53
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
54
+ {
55
+ body: fixture(:campaigns_all),
56
+ status: 429,
57
+ headers: {
58
+ 'x-account-rate-limit-limit': 10000,
59
+ 'x-account-rate-limit-remaining': 9999,
60
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
61
+ }
62
+ },
63
+ {
64
+ body: fixture(:campaigns_all),
65
+ status: 200
66
+ }
67
+ )
68
+ cusor = described_class.all(account1)
69
+ expect(cusor).to be_instance_of(Cursor)
70
+ expect(stub).to have_been_requested.times(2)
71
+ end
72
+
73
+ it 'test rate-limit handle - fail' do
74
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
75
+ {
76
+ body: fixture(:campaigns_all),
77
+ status: 429,
78
+ headers: {
79
+ 'x-account-rate-limit-limit': 10000,
80
+ 'x-account-rate-limit-remaining': 9999,
81
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
82
+ }
83
+ },
84
+ {
85
+ body: fixture(:campaigns_all),
86
+ status: 429,
87
+ headers: {
88
+ 'x-account-rate-limit-limit': 10000,
89
+ 'x-account-rate-limit-remaining': 9999,
90
+ 'x-account-rate-limit-reset': 4102444800
91
+ }
92
+ }
93
+ )
94
+ begin
95
+ cusor = described_class.all(account1)
96
+ rescue RateLimit => e
97
+ cusor = e
98
+ end
99
+ expect(cusor).to be_instance_of(RateLimit)
100
+ expect(stub).to have_been_requested.times(2)
101
+ expect(cusor.reset_at).to eq 4102444800
102
+ end
103
+
104
+ it 'test rate-limit handle with retry - case 1' do
105
+ # scenario:
106
+ # - 500 (retry) -> 429 (handle rate limit) -> 200 (end)
107
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
108
+ {
109
+ body: fixture(:campaigns_all),
110
+ status: 500,
111
+ headers: {
112
+ 'x-account-rate-limit-limit': 10000,
113
+ 'x-account-rate-limit-remaining': 9999,
114
+ 'x-account-rate-limit-reset': 4102444800
115
+ }
116
+ },
117
+ {
118
+ body: fixture(:campaigns_all),
119
+ status: 429,
120
+ headers: {
121
+ 'x-account-rate-limit-limit': 10000,
122
+ 'x-account-rate-limit-remaining': 9999,
123
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
124
+ }
125
+ },
126
+ {
127
+ body: fixture(:campaigns_all),
128
+ status: 200
129
+ }
130
+ )
131
+ cusor = described_class.all(account2)
132
+ expect(cusor).to be_instance_of(Cursor)
133
+ expect(stub).to have_been_requested.times(3)
134
+ end
135
+
136
+ it 'test rate-limit handle with retry - case 2' do
137
+ # scenario:
138
+ # - 429 (handle rate limit) -> 500 (retry) -> 200 (end)
139
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
140
+ {
141
+ body: fixture(:campaigns_all),
142
+ status: 429,
143
+ headers: {
144
+ 'x-account-rate-limit-limit': 10000,
145
+ 'x-account-rate-limit-remaining': 9999,
146
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
147
+ }
148
+ },
149
+ {
150
+ body: fixture(:campaigns_all),
151
+ status: 500,
152
+ headers: {
153
+ 'x-account-rate-limit-limit': 10000,
154
+ 'x-account-rate-limit-remaining': 9999,
155
+ 'x-account-rate-limit-reset': 4102444800
156
+ }
157
+ },
158
+ {
159
+ body: fixture(:campaigns_all),
160
+ status: 200
161
+ }
162
+ )
163
+ cusor = described_class.all(account2)
164
+ expect(cusor).to be_instance_of(Cursor)
165
+ expect(stub).to have_been_requested.times(3)
166
+ end
167
+
168
+ it 'test rate-limit header access from cusor class' do
169
+ stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
170
+ {
171
+ body: fixture(:campaigns_all),
172
+ status: 200,
173
+ headers: {
174
+ 'x-account-rate-limit-limit': 10000,
175
+ 'x-account-rate-limit-remaining': 9999,
176
+ 'x-account-rate-limit-reset': 4102444800
177
+ }
178
+ }
179
+ )
180
+ cusor = described_class.all(account)
181
+ expect(cusor).to be_instance_of(Cursor)
182
+ expect(cusor.account_rate_limit_limit).to eq 10000
183
+ expect(cusor.account_rate_limit_remaining).to eq 9999
184
+ expect(cusor.account_rate_limit_reset).to eq 4102444800
185
+ end
186
+
187
+ it 'test rate-limit header access from resource class' do
188
+ stub_request(:any, /accounts\/2iqph\/campaigns\/2wap7/).to_return(
189
+ {
190
+ body: fixture(:campaigns_load),
191
+ status: 200,
192
+ headers: {
193
+ 'x-account-rate-limit-limit': 10000,
194
+ 'x-account-rate-limit-remaining': 9999,
195
+ 'x-account-rate-limit-reset': 4102444800
196
+ }
197
+ }
198
+ )
199
+
200
+ campaign = described_class.load(account, '2wap7')
201
+ resource = "/#{TwitterAds::API_VERSION}/accounts/2iqph/campaigns/2wap7"
202
+ params = {}
203
+ response = TwitterAds::Request.new(client, :get, resource, params: params).perform
204
+ data = campaign.from_response(response.body[:data], response.headers)
205
+ expect(data).to be_instance_of(Campaign)
206
+ expect(data.account_rate_limit_limit).to eq 10000
207
+ expect(data.account_rate_limit_remaining).to eq 9999
208
+ expect(data.account_rate_limit_reset).to eq 4102444800
209
+ end
210
+
211
+ it 'test rate-limit header access check instance variables not conflicting' do
212
+ stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
213
+ {
214
+ body: fixture(:campaigns_all),
215
+ status: 200,
216
+ headers: {
217
+ 'x-account-rate-limit-limit': 10000,
218
+ 'x-account-rate-limit-remaining': 9999,
219
+ 'x-account-rate-limit-reset': 4102444800
220
+ }
221
+ },
222
+ {
223
+ body: fixture(:campaigns_all),
224
+ status: 200,
225
+ headers: {
226
+ 'x-account-rate-limit-limit': 10000,
227
+ 'x-account-rate-limit-remaining': 9998,
228
+ 'x-account-rate-limit-reset': 4102444800
229
+ }
230
+ }
231
+ )
232
+
233
+ campaign_first = described_class.all(account)
234
+ campaign_second = described_class.all(account)
235
+
236
+ expect(campaign_first).to be_instance_of(Cursor)
237
+ expect(campaign_first.account_rate_limit_limit).to eq 10000
238
+ expect(campaign_first.account_rate_limit_remaining).to eq 9999
239
+ expect(campaign_first.account_rate_limit_reset).to eq 4102444800
240
+
241
+ expect(campaign_second).to be_instance_of(Cursor)
242
+ expect(campaign_second.account_rate_limit_limit).to eq 10000
243
+ expect(campaign_second.account_rate_limit_remaining).to eq 9998
244
+ expect(campaign_second.account_rate_limit_reset).to eq 4102444800
245
+ end
246
+
247
+ end