appnexusapi 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -4
  3. data/UPGRADING.md +10 -0
  4. data/appnexusapi.gemspec +2 -0
  5. data/env_example +1 -3
  6. data/lib/appnexusapi/advertiser_resource.rb +2 -0
  7. data/lib/appnexusapi/advertiser_service.rb +2 -0
  8. data/lib/appnexusapi/campaign_resource.rb +2 -0
  9. data/lib/appnexusapi/campaign_service.rb +2 -0
  10. data/lib/appnexusapi/connection.rb +19 -5
  11. data/lib/appnexusapi/content_category_service.rb +3 -1
  12. data/lib/appnexusapi/creative_service.rb +0 -10
  13. data/lib/appnexusapi/payment_rule_resource.rb +2 -0
  14. data/lib/appnexusapi/payment_rule_service.rb +2 -0
  15. data/lib/appnexusapi/placement_resource.rb +2 -0
  16. data/lib/appnexusapi/placement_service.rb +2 -0
  17. data/lib/appnexusapi/profile_resource.rb +2 -0
  18. data/lib/appnexusapi/profile_service.rb +2 -0
  19. data/lib/appnexusapi/publisher_resource.rb +2 -0
  20. data/lib/appnexusapi/publisher_service.rb +2 -0
  21. data/lib/appnexusapi/resource.rb +5 -4
  22. data/lib/appnexusapi/service.rb +16 -8
  23. data/lib/appnexusapi/site_resource.rb +2 -0
  24. data/lib/appnexusapi/site_service.rb +2 -0
  25. data/lib/appnexusapi/version.rb +1 -1
  26. data/spec/connection_spec.rb +13 -3
  27. data/spec/creative_service_spec.rb +33 -22
  28. data/spec/fixtures/vcr/advertiser_get.yml +108 -0
  29. data/spec/fixtures/vcr/campaign_life_cycle.yml +524 -0
  30. data/spec/fixtures/vcr/content_category_crud.yml +398 -0
  31. data/spec/fixtures/vcr/creative_service_create.yml +216 -0
  32. data/spec/fixtures/vcr/creative_service_delete.yml +354 -0
  33. data/spec/fixtures/vcr/creative_service_get.yml +214 -0
  34. data/spec/fixtures/vcr/creative_service_update.yml +266 -0
  35. data/spec/fixtures/vcr/domain_list_limits.yml +105 -0
  36. data/spec/fixtures/vcr/line_item_life_cycle.yml +521 -0
  37. data/spec/fixtures/vcr/member_get.yml +150 -0
  38. data/spec/fixtures/vcr/object_limit_info.yml +105 -0
  39. data/spec/fixtures/vcr/payment_rule_lifecycle.yml +346 -0
  40. data/spec/fixtures/vcr/placement_service_default_placement.yml +166 -0
  41. data/spec/fixtures/vcr/profile_life_cycle.yml +349 -0
  42. data/spec/fixtures/vcr/profile_limits.yml +105 -0
  43. data/spec/fixtures/vcr/publisher_crud.yml +282 -0
  44. data/spec/fixtures/vcr/site_lifecycle.yml +284 -0
  45. data/spec/fixtures/vcr/user_login.yml +107 -0
  46. data/spec/fixtures/vcr/user_update.yml +288 -0
  47. data/spec/integration/advertiser_spec.rb +11 -0
  48. data/spec/integration/campaign_spec.rb +38 -0
  49. data/spec/integration/content_category_spec.rb +22 -0
  50. data/spec/integration/line_item_spec.rb +27 -0
  51. data/spec/integration/member_spec.rb +9 -0
  52. data/spec/integration/payment_rule_spec.rb +33 -0
  53. data/spec/integration/placement_spec.rb +29 -0
  54. data/spec/integration/profile_spec.rb +29 -0
  55. data/spec/integration/publisher_spec.rb +20 -0
  56. data/spec/integration/site_spec.rb +32 -0
  57. data/spec/integration/user_spec.rb +42 -0
  58. data/spec/object_limit_service_spec.rb +14 -11
  59. data/spec/spec_helper.rb +52 -25
  60. data/spec/support/shared_contexts.rb +28 -0
  61. metadata +109 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abcd460b5e9565ea41c0960fdf71ad3f8a082878
4
- data.tar.gz: e2bca3f9ae62267d8dd7bad5a023fc8a399c1839
3
+ metadata.gz: 0184db6bca5286e5ec3ac206cf0d964ce78484e0
4
+ data.tar.gz: dec7fe42720c2c0b89dcaa55bbb142bfce2721b5
5
5
  SHA512:
6
- metadata.gz: 4fc78be7f3583ee7476ebdbabeed39739a4018617f2ac126e7733fb83c1a12d9ee19eb679218ddea7c9400041995f206bd5b4ce3ba02d8c9939e02b0d311cee4
7
- data.tar.gz: 8e3c2db20e52f89b720c82758ef55567806f5d8a542f111bafcc3355053f8fc5b1102b76cf4e2da4b459a3c816942171e009e89b4a413cf03b5b5004b6146490
6
+ metadata.gz: fb39def781471b2b91f8803116d0c71d734dca49ddaca2fec95e6d104b1f7e21c61aa9c0d55fa11300a69d6d95e7e961a7479dc34ef319a8791e9032483f5aa4
7
+ data.tar.gz: 6932d3ac84d2f075c5cb71c4d5a97a0a618ed2af94b7f41091f2776dff59c82099527a8ac6b2c319d9577c61f6725b1f7a322e115f24fbd1e3b76686dfe49535
data/README.md CHANGED
@@ -27,9 +27,9 @@ Establish a connection:
27
27
 
28
28
  # Defaults to connecting to https://api.appnexus.com/ but you can optionally pass a uri to
29
29
  # connect to another endpoint, e.g. the staging site could be
30
- # uri: 'http://api.sand-08.adnxs.net
30
+ # "uri" => 'http://api-test.appnexus.com',
31
+
31
32
 
32
-
33
33
  )
34
34
 
35
35
  Use a Service:
@@ -40,7 +40,27 @@ Use a Service:
40
40
  # and returns an AppnexusApi::Resource object which is a wrapper around the JSON
41
41
  member = member_service.get.first
42
42
 
43
- creative_service = AppnexusApi::CreativeService.new(connection, member.id)
43
+ line_item_service = AppnexusApi::LineItemService.new(connection)
44
+ line_item = line_item_service.get.first
45
+ line_item = line_item_service.get({advertiser_id: 12345}).first
46
+
47
+ # create a new object
48
+ url_params = { advertiser_id: 12345 }
49
+ body_params = { name: "some line item", code: "line item code"}
50
+
51
+ line_item = line_item_service.create(url_params, body_params)
52
+ line_item.state
53
+
54
+
55
+ # update an object
56
+ update_params = { state: "inactive" }
57
+ json_result = line_item.update(url_params, update_params)
58
+
59
+ # delete an object
60
+ line_item.delete(url_params)
61
+
62
+ # this raises an AppnexusApi::UnprocessableEntity, not a 404 as it should
63
+ line_item_service.get(line_item.id)
44
64
 
45
65
  new_creative = {
46
66
  "content" => "<iframe src='helloword.html'></iframe>",
@@ -53,13 +73,22 @@ Use a Service:
53
73
 
54
74
  ## Testing
55
75
 
56
- There is a rudimentary test suite that centers around creatives/creative_service. To use it, you'll need to copy the `env_example` file to `.env` and replace the values with your correct values for your account. After that, a simple `bundle exec rspec spec` will run the test suite
76
+ ### Running Existing Specs
77
+ ```
78
+ bundle exec rspec
79
+ ```
80
+
81
+ ### Writing New Specs or Updating Old Ones
82
+ This library uses [VCR](https://github.com/vcr/vcr) and Webmock to record API call HTTP data and play it back when you run the specs. To write new specs or update old ones, however, you will need to actually provide a valid login/password in a `.env` file (see [`env_example`](env_example) for an example) before launching the specs.
83
+
84
+ To update a spec, simply remove the relevant file from [spec/fixtures/vcr](spec/fixtures/vcr) (and setup the username/password) before launching `rspec`; the changes will be recorded automatically by VCR.
57
85
 
58
86
 
59
87
  ## Contributing
60
88
 
61
89
  1. Fork it
62
90
  2. Create your feature branch (`git checkout -b my-new-feature`)
91
+ 3. Make changes (with tests -- at least integration tests, please)
63
92
  3. Commit your changes (`git commit -am 'Added some feature'`)
64
93
  4. Push to the branch (`git push origin my-new-feature`)
65
94
  5. Create new Pull Request
data/UPGRADING.md ADDED
@@ -0,0 +1,10 @@
1
+ Upgrading from 0.1.x or earlier
2
+
3
+ - create/update/delete methods now take an additional argument to pass, e.g.
4
+ "?publisher_id=5" where appropriate and required. This goes before the
5
+ "body" hash.
6
+
7
+
8
+ the implementation should probably read advertiser_id/publisher_id from
9
+ the object for update/delete but in the cases that both are present, I'm
10
+ unsure which one (or both?) should be sent
data/appnexusapi.gemspec CHANGED
@@ -25,4 +25,6 @@ Gem::Specification.new do |gem|
25
25
  gem.add_development_dependency 'rspec'
26
26
  gem.add_development_dependency 'dotenv'
27
27
  gem.add_development_dependency 'pry'
28
+ gem.add_development_dependency 'vcr'
29
+ gem.add_development_dependency 'webmock'
28
30
  end
data/env_example CHANGED
@@ -1,5 +1,3 @@
1
- APPNEXUS_MEMBER_ID=123
2
1
  APPNEXUS_USERNAME=appnexus_user
3
2
  APPNEXUS_PASSWORD=HGsgwDdxDVWM
4
- APPNEXUS_URI=https://sand.api.adnxs.com
5
-
3
+ APPNEXUS_URI=https://api-test.appnexus.com
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::AdvertiserResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::AdvertiserService < AppnexusApi::Service
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::CampaignResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::CampaignService < AppnexusApi::Service
2
+ end
@@ -6,7 +6,7 @@ class AppnexusApi::Connection
6
6
  RATE_EXCEEDED_DEFAULT_TIMEOUT = 15
7
7
  # Inexplicably, sandbox uses the correct code of 429, while production uses 405? so
8
8
  # we just rely on the error message
9
- RATE_EXCEEDED_ERROR = "RATE_EXCEEDED".freeze
9
+ RATE_EXCEEDED_ERROR = 'RATE_EXCEEDED'.freeze
10
10
 
11
11
  def initialize(config)
12
12
  @config = config
@@ -44,7 +44,11 @@ class AppnexusApi::Connection
44
44
 
45
45
  def get(route, params={}, headers={})
46
46
  params = params.delete_if {|key, value| value.nil? }
47
- run_request(:get, @connection.build_url(route, params), nil, headers)
47
+ run_request(:get, build_url(route, params), nil, headers)
48
+ end
49
+
50
+ def build_url(route, params)
51
+ @connection.build_url(route, params)
48
52
  end
49
53
 
50
54
  def post(route, body=nil, headers={})
@@ -60,16 +64,17 @@ class AppnexusApi::Connection
60
64
  end
61
65
 
62
66
  def run_request(method, route, body, headers)
63
- login if !is_authorized?
67
+ login unless is_authorized?
64
68
  response = {}
65
69
  begin
66
70
  loop do
67
- response = @connection.run_request(
71
+ response = run_request_only(
68
72
  method,
69
73
  route,
70
74
  body,
71
75
  { 'Authorization' => @token }.merge(headers)
72
76
  )
77
+ break if response.body.empty? # Log level data download service returns a body of ""
73
78
  break unless response.body.fetch('response', {})['error_code'] == RATE_EXCEEDED_ERROR
74
79
  wait_time = response.headers['retry-after'] || RATE_EXCEEDED_DEFAULT_TIMEOUT
75
80
  log.info("received rate exceeded. wait time: #{wait_time}s")
@@ -81,7 +86,7 @@ class AppnexusApi::Connection
81
86
  else
82
87
  @retry = true
83
88
  logout
84
- run_request(method, route, body, headers)
89
+ response = run_request(method, route, body, headers)
85
90
  end
86
91
  rescue Faraday::Error::TimeoutError => _e
87
92
  raise AppnexusApi::Timeout, 'Timeout'
@@ -91,4 +96,13 @@ class AppnexusApi::Connection
91
96
  log.debug(response.body)
92
97
  response
93
98
  end
99
+
100
+ def run_request_only(method, route, body, headers)
101
+ @connection.run_request(
102
+ method,
103
+ route,
104
+ body,
105
+ { 'Authorization' => @token }.merge(headers)
106
+ )
107
+ end
94
108
  end
@@ -1,7 +1,6 @@
1
1
  class AppnexusApi::ContentCategoryService < AppnexusApi::Service
2
2
 
3
3
  def initialize(connection)
4
- @read_only = true
5
4
  super(connection)
6
5
  end
7
6
 
@@ -9,4 +8,7 @@ class AppnexusApi::ContentCategoryService < AppnexusApi::Service
9
8
  "content_categories"
10
9
  end
11
10
 
11
+ def plural_uri_name
12
+ "content-categories"
13
+ end
12
14
  end
@@ -1,12 +1,2 @@
1
1
  class AppnexusApi::CreativeService < AppnexusApi::Service
2
-
3
- def initialize(connection, member_id)
4
- @member_id = member_id
5
- super(connection)
6
- end
7
-
8
- def uri_suffix
9
- "#{super}/#{@member_id}"
10
- end
11
-
12
2
  end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PaymentRuleResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PaymentRuleService < AppnexusApi::Service
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PlacementResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PlacementService < AppnexusApi::Service
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::ProfileResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::ProfileService < AppnexusApi::Service
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PublisherResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::PublisherService < AppnexusApi::Service
2
+ end
@@ -8,13 +8,14 @@ class AppnexusApi::Resource
8
8
  @dbg_info = dbg_info
9
9
  end
10
10
 
11
- def update(attributes={})
12
- resource = @service.update(id, attributes)
11
+ def update(route_params={}, body_params={})
12
+ resource = @service.update(id, route_params, body_params)
13
13
  @json = resource.raw_json
14
+ self
14
15
  end
15
16
 
16
- def delete
17
- @service.delete(id)
17
+ def delete(route_params={})
18
+ @service.delete(id, route_params)
18
19
  end
19
20
 
20
21
  def raw_json
@@ -61,10 +61,11 @@ class AppnexusApi::Service
61
61
  responses
62
62
  end
63
63
 
64
- def create(attributes={})
64
+ def create(route_params={}, body={})
65
65
  raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
66
- attributes = { name => attributes }
67
- response = @connection.post(uri_suffix, attributes).body['response']
66
+ body = { uri_name => body }
67
+ route = @connection.build_url(uri_suffix, route_params)
68
+ response = @connection.post(route, body).body['response']
68
69
  if response['error_id']
69
70
  response.delete('dbg')
70
71
  raise AppnexusApi::BadRequest.new(response.inspect)
@@ -72,10 +73,11 @@ class AppnexusApi::Service
72
73
  parse_response(response).first
73
74
  end
74
75
 
75
- def update(id, attributes={})
76
+ def update(id, route_params={}, body={})
76
77
  raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
77
- attributes = { name => attributes }
78
- response = @connection.put([uri_suffix, id].join('/'), attributes).body['response']
78
+ body = { uri_name => body }
79
+ route = @connection.build_url(uri_suffix, route_params.merge("id" => id))
80
+ response = @connection.put(route, body).body['response']
79
81
  if response['error_id']
80
82
  response.delete('dbg')
81
83
  raise AppnexusApi::BadRequest.new(response.inspect)
@@ -83,9 +85,15 @@ class AppnexusApi::Service
83
85
  parse_response(response).first
84
86
  end
85
87
 
86
- def delete(id)
88
+ def delete(id, route_params)
87
89
  raise(AppnexusApi::NotImplemented, "Service is read-only.") if @read_only
88
- @connection.delete([uri_suffix, id].join('/')).body['response']
90
+ route = @connection.build_url(uri_suffix, route_params.merge("id" => id))
91
+ response = @connection.delete(route).body['response']
92
+ if response['error_id']
93
+ response.delete('dbg')
94
+ raise AppnexusApi::BadRequest.new(response.inspect)
95
+ end
96
+ response
89
97
  end
90
98
 
91
99
  def parse_response(response)
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::SiteResource < AppnexusApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class AppnexusApi::SiteService < AppnexusApi::Service
2
+ end
@@ -1,3 +1,3 @@
1
1
  module AppnexusApi
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,11 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe AppnexusApi::Connection do
4
+ subject { AppnexusApi::Connection.new({}) }
4
5
 
5
6
  it 'allows no logger to be specified' do
6
- expect do
7
- AppnexusApi::CreativeService.new(connection_with_null_logger, ENV['APPNEXUS_MEMBER_ID'])
8
- end.to_not raise_error
7
+ expect { AppnexusApi::CreativeService.new(connection_with_null_logger) }.to_not raise_error
9
8
  end
10
9
 
10
+ it 'returns data from expiration' do
11
+ #stub to raise error the first time and then return []
12
+ counter = 0
13
+ allow(subject).to receive(:login)
14
+ allow(subject).to receive(:run_request_only) do |arg|
15
+ counter += 1
16
+ raise AppnexusApi::Unauthorized.new if counter == 1
17
+ Faraday::Response.new(body: { not_an_error: 1 })
18
+ end
19
+ expect(subject.run_request(:get, 'http://localhost', nil, {})).not_to eq({})
20
+ end
11
21
  end
@@ -1,12 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe AppnexusApi::CreativeService do
4
- let(:creative_service) do
5
- AppnexusApi::CreativeService.new(connection, ENV['APPNEXUS_MEMBER_ID'])
6
- end
4
+ include_context 'with an advertiser'
5
+
6
+ let(:creative_service) { described_class.new(connection) }
7
7
  let(:new_creative) do
8
8
  {
9
- 'campaign' => 'default campaign',
9
+ 'name' => 'rspec test creative',
10
10
  'content' => '<iframe src="helloword.html"></iframe>',
11
11
  'width' => '300',
12
12
  'height' => '250',
@@ -26,46 +26,57 @@ describe AppnexusApi::CreativeService do
26
26
  threads = []
27
27
  10.times do
28
28
  threads << Thread.new do
29
- creative = creative_service.create(new_creative)
29
+ creative = creative_service.create(advertiser_url_params, new_creative)
30
30
  puts creative.dbg_info
31
31
  end
32
32
  end
33
33
  threads.map(&:join)
34
34
  end
35
35
  end.to_not raise_error
36
+
37
+ advertiser.delete
36
38
  end
37
39
 
38
40
  it 'supports a get operation' do
39
- expect do
40
- creative_service.get('start_element' => 0, 'num_elements' => 1)
41
- end.to_not raise_error
41
+ VCR.use_cassette('creative_service_get') do
42
+ expect { creative_service.get('start_element' => 0, 'num_elements' => 1) }.to_not raise_error
43
+ advertiser.delete
44
+ end
42
45
  end
43
46
 
44
47
  context 'creating a new creative' do
45
48
  it 'supports creating a new creative' do
46
- creative = creative_service.create(new_creative)
47
- expect(creative.width).to eq 300
48
- expect(creative.height).to eq 250
49
+ VCR.use_cassette('creative_service_create') do
50
+ creative = creative_service.create(advertiser_url_params, new_creative)
51
+ expect(creative.width).to eq(300)
52
+ expect(creative.height).to eq(250)
53
+ advertiser.delete
54
+ end
49
55
  end
50
56
  end
51
57
 
52
58
  context 'an existing creative' do
53
- let(:existing_creative) { creative_service.create(new_creative) }
59
+ let(:existing_creative) { creative_service.create(advertiser_url_params, new_creative) }
54
60
 
55
61
  it 'supports changing attributes with the update action' do
56
- expect(existing_creative.campaign).to eq 'default campaign'
57
- existing_creative.update('campaign' => 'My Best Campaign Yet')
58
- expect(existing_creative.campaign).to eq 'My Best Campaign Yet'
62
+ VCR.use_cassette('creative_service_update') do
63
+ expect(existing_creative.code).to be_nil
64
+ existing_creative.update(advertiser_url_params, code: "abc")
65
+ expect(existing_creative.code).to eq "abc"
66
+ advertiser.delete
67
+ end
59
68
  end
60
69
 
61
70
  it 'supports removing the creative' do
62
- id = existing_creative.id
63
- creative = creative_service.get('id' => id).first
64
- expect(creative.id).to eq id
65
- existing_creative.delete
66
- creative = creative_service.get('id' => id)
67
- expect(creative).to be_nil
71
+ VCR.use_cassette('creative_service_delete') do
72
+ id = existing_creative.id
73
+ creative = creative_service.get('id' => id).first
74
+ expect(creative.id).to eq id
75
+ existing_creative.delete(advertiser_url_params)
76
+ creative = creative_service.get('id' => id)
77
+ expect(creative).to be_nil
78
+ advertiser.delete
79
+ end
68
80
  end
69
81
  end
70
82
  end
71
-