ad_leads 0.1.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ca5e0966af39aecdd5c29834dfcef08da19d37f
4
+ data.tar.gz: 0a34abc322f7d6db6220d349e4eca701b766d5f0
5
+ SHA512:
6
+ metadata.gz: 826299ae9ad0040440699bc5192880bc2573fa61cc621bcd55949f1c9b2d98ad5d595fe928d7c366ccdffd3d56fe589f9150d87c16f9b0080ffa6f1ae9e1660d
7
+ data.tar.gz: 1f59acc22d34c6eb258ee937d5720216f771f6f4772bc17f6f7db19707a4ffebe6b60e31464054649a497727df4ed05631514925e00df800a6c0b9202521e2b8
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .env
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ad_leads.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dinshaw
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # AdLeads
2
+ Wrapper for the Adleads JSON api.
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ gem 'ad_leads'
9
+
10
+ And then execute:
11
+
12
+ $ bundle
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install ad_leads
17
+
18
+ ## Usage
19
+ Steps are outline by request here - https://api.adleads.com/doc/index.html#tutorial
20
+
21
+ AdLeads.configure do |config|
22
+ config.client_id = ENV['AD_LEADS_CLIENT_ID'],
23
+ config.principle = ENV['AD_LEADS_PRINCIPLE'],
24
+ config.private_key = ENV['AD_LEADS_PRIVATE_KEY']
25
+ end
26
+
27
+ client = AdLeads::Client.new
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( http://github.com/<my-github-username>/ad_leads/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/ad_leads.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ad_leads/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ad_leads"
8
+ spec.version = AdLeads::VERSION
9
+ spec.authors = ["Dinshaw"]
10
+ spec.email = ["dgobhai@constantcontact.com"]
11
+ spec.summary = %q{Ruby Wrapper for AdLeads API}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'faraday', '0.9.0'
21
+ spec.add_dependency 'httpclient', '2.3.4.1'
22
+ spec.add_dependency 'jwt', '0.1.11'
23
+ spec.add_development_dependency 'rspec', '~> 2.14'
24
+ spec.add_development_dependency 'bundler', "~> 1.5"
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'pry'
27
+ end
@@ -0,0 +1,19 @@
1
+ class AdLeads::Ad < AdLeads::Base
2
+ # params = {
3
+ # 'type' => 'Mobile',
4
+ # 'name' => 'test mobile ad',
5
+ # 'headerText' => 'get your ad on this phone today',
6
+ # 'bodyText' => 'this is mobile ad body copy'
7
+ # }
8
+
9
+ attr_accessor :creative_group_id
10
+
11
+ def initialize(creative_group_id)
12
+ @creative_group_id = creative_group_id
13
+ end
14
+
15
+ def root_path
16
+ "/creativegroups/#{creative_group_id}/creatives"
17
+ end
18
+
19
+ end
@@ -0,0 +1,19 @@
1
+ class AdLeads::Base
2
+ attr_accessor :response
3
+
4
+ def create!(params)
5
+ self.response = client.post(root_path, params)
6
+ end
7
+
8
+ def client
9
+ @client ||= AdLeads::Client.new
10
+ end
11
+
12
+ def id
13
+ @id ||= JSON.parse(response.body)['data'].first
14
+ end
15
+
16
+ def status
17
+ response.status
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ class AdLeads::Campaign < AdLeads::Base
2
+ include AdLeads::Etag
3
+
4
+ # params = {
5
+ # 'name' => 'test',
6
+ # 'verticals' => 82,
7
+ # 'offerIncentiveCategory' => 5,
8
+ # 'collectedFields' => 'firstname,lastname,email,companyname',
9
+ # 'budget' => 50,
10
+ # 'creativeGroups' => creative_group.id
11
+ # }
12
+
13
+ def update!(params)
14
+ client.post(campaign_path, params)
15
+ end
16
+
17
+ def verify!
18
+ client.get(verify_campaign_path)
19
+ end
20
+
21
+ def launch!
22
+ with_etag do
23
+ client.post(launch_campaign_path, etag: etag)
24
+ end
25
+ end
26
+
27
+ def campaign_path
28
+ root_path + "/#{id}"
29
+ end
30
+ alias :etag_path :campaign_path
31
+
32
+ def launch_campaign_path
33
+ campaign_path + '/launch'
34
+ end
35
+
36
+ def root_path
37
+ '/campaigns'
38
+ end
39
+
40
+ def verify_campaign_path
41
+ campaign_path + '/plan'
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ class CampaignHelpers
2
+
3
+ def creative_group_params
4
+ {
5
+ 'name' => @ad_campaign_obj[:campaign_info][:name],
6
+ 'productName' => @ad_campaign_obj[:content_info][:name],
7
+ 'active' => @ad_campaign_obj[:content_info][:active],
8
+ 'privacyPolicyUrl' => @ad_campaign_obj[:content_info][:privacy]
9
+ }
10
+ end
11
+
12
+ def ad_params
13
+ {
14
+ 'name' => @ad_campaign_obj[:content_info][:name]
15
+ 'type' => @ad_campaign_obj[:content_info][:type],
16
+ ##mobile
17
+ 'headerText' => @ad_campaign_obj[:content_info][:headerText],
18
+ 'bodyText' => @ad_campaign_obj[:content_info][:bodyText],
19
+ ##email
20
+ 'fromAddress' => @ad_campaign_obj[:campaign_info][:email],
21
+ 'subject' => @ad_campaign_obj[:content_info][:subject],
22
+ 'companyName' => @ad_campaign_obj[:campaign_info][:dba],
23
+ 'mailingAddress' => @ad_campaign_obj[:campaign_info][:address],
24
+ 'calltoAction' => @ad_campaign_obj[:campaign_info][:cta],
25
+ 'preHeader' => @ad_campaign_obj[:campaign_info][:pre_header]
26
+ }
27
+ end
28
+
29
+ def content_holder_params
30
+ {
31
+ 'type' => @ad_campaign_obj[:content_info][:image_type]
32
+ }
33
+ end
34
+
35
+ def campaign_params
36
+ {
37
+ 'name' => @ad_campaign_obj[:content_info][:name],
38
+ 'verticals' => @ad_campaign_obj[:content_info][:verticals],
39
+ 'offerIncentiveCategory' => @ad_campaign_obj[:content_info][:incentives],
40
+ 'collectedFields' => @ad_campaign_obj[:content_info][:collected_fields],
41
+ 'budget' => @ad_campaign_obj[:campaign_info][:spend],
42
+ 'creativeGroups' => @creative_group_id
43
+ }
44
+ end
@@ -0,0 +1,106 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module AdLeads
5
+ class Client
6
+ attr_accessor *Configuration::VALID_CONFIG_KEYS
7
+
8
+ def initialize(options={})
9
+ merged_options = AdLeads.options.merge(options)
10
+ Configuration::VALID_CONFIG_KEYS.each do |key|
11
+ send("#{key}=", merged_options[key])
12
+ end
13
+ end
14
+
15
+ def configure_campaign_signups(ad_campaign_id, etag, params = {})
16
+ path = "/campaigns/#{ad_campaign_id}/signupdelivery"
17
+ request(:post, path, params.merge(etag: etag))
18
+ end
19
+
20
+ def get_campaign_status(ad_campaign_id)
21
+ response = get("/campaigns/#{ad_campaign_id}")
22
+ JSON.parse(response.body)['status']
23
+ end
24
+
25
+ def get_reports(params)
26
+ get("/reports/campaign/report", params)
27
+ end
28
+
29
+ def pause_campaign(ad_campaign_id, etag)
30
+ path = "/campaigns/#{ad_campaign_id}/pause"
31
+ request(:post, path, etag: etag)
32
+ end
33
+
34
+ def get(path, params = {})
35
+ request(:get, path, params)
36
+ end
37
+
38
+ def post(path, params = {})
39
+ request(:post, path, params)
40
+ end
41
+
42
+ private
43
+
44
+ def etag_opts(etag)
45
+ {
46
+ headers: { 'If-Match' => etag },
47
+ multipart: true
48
+ }
49
+ end
50
+
51
+ def connection(opts = {})
52
+ opts[:headers] ||= { 'Accept' => 'application/json' }
53
+
54
+ Faraday.new(url: endpoint) do |faraday|
55
+ faraday.headers = opts[:headers]
56
+ faraday.request :multipart if opts[:multipart]
57
+
58
+ faraday.authorization :Bearer, token
59
+ faraday.adapter :httpclient
60
+ faraday.request :url_encoded
61
+ end
62
+ end
63
+
64
+ def token
65
+ @token ||= AdLeads::Token.new(client_id: client_id, principle: principle).token
66
+ end
67
+
68
+ def request(method, path, params = {})
69
+ etag = params.delete(:etag)
70
+ opts = etag ? etag_opts(etag) : {}
71
+
72
+ response = connection(opts).send(method, path) do |request|
73
+ request.params = params if method == :get
74
+ request.body = params if method == :post
75
+ end
76
+
77
+ case response.status
78
+ when 401
79
+ raise AuthError.new(<<-ERROR)
80
+ token: #{token},
81
+ method: #{method},
82
+ endpoint: #{endpoint},
83
+ path: #{path}",
84
+ body: #{response.body}
85
+ ERROR
86
+ when 500
87
+ raise ApiError.new(<<-ERROR)
88
+ endpoint: #{endpoint},
89
+ method: #{method},
90
+ path: #{path}",
91
+ body: #{response.body}
92
+ ERROR
93
+
94
+ else
95
+ response
96
+ end
97
+
98
+ # rescue Faraday::Error::TimeoutError, Timeout::Error => error
99
+ # rescue Faraday::Error::ClientError, JSON::ParserError => error
100
+ end
101
+ end
102
+
103
+ class AuthError < StandardError; end
104
+ class ApiError < StandardError; end
105
+ end
106
+ 2
@@ -0,0 +1,43 @@
1
+ module AdLeads
2
+ module Configuration
3
+
4
+ def configure
5
+ yield self
6
+ end
7
+
8
+ VALID_CONNECTION_KEYS = [:endpoint, :token_endpoint, :user_agent].freeze
9
+ VALID_OPTIONS_KEYS = [:client_id, :private_key, :principle, :format].freeze
10
+ VALID_CONFIG_KEYS = VALID_CONNECTION_KEYS + VALID_OPTIONS_KEYS
11
+
12
+ DEFAULT_ENDPOINT = 'https://api.adleads.com'
13
+ DEFAULT_TOKEN_ENDPOINT = 'https://auth.adleads.com'
14
+ DEFAULT_USER_AGENT = "AdLeads API Ruby Gem #{AdLeads::VERSION}".freeze
15
+
16
+ DEFAULT_FORMAT = :json
17
+ DEFAULT_CLIENT_ID = 'client_id'
18
+ DEFAULT_PRINCIPLE = 'principle'
19
+ DEFAULT_PRIVATE_KEY = 'private_key'
20
+
21
+ attr_accessor *VALID_CONFIG_KEYS
22
+
23
+ # Make sure we have the default values set when we get 'extended'
24
+ def self.extended(base)
25
+ base.reset
26
+ end
27
+
28
+ def options
29
+ Hash[ * VALID_CONFIG_KEYS.map { |key| [key, send(key)] }.flatten ]
30
+ end
31
+
32
+ def reset
33
+ self.endpoint = DEFAULT_ENDPOINT
34
+ self.token_endpoint = DEFAULT_TOKEN_ENDPOINT
35
+ self.user_agent = DEFAULT_USER_AGENT
36
+ self.client_id = DEFAULT_CLIENT_ID
37
+ self.private_key = DEFAULT_PRIVATE_KEY
38
+ self.principle = DEFAULT_PRINCIPLE
39
+ self.format = DEFAULT_FORMAT
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ class AdLeads::CreativeGroup < AdLeads::Base
2
+
3
+ # params = {
4
+ # 'name' => 'test creative group',
5
+ # 'productName' => 'test product',
6
+ # 'privacyPolicyUrl' => 'http://privacy_url'
7
+ # }
8
+
9
+ def root_path
10
+ '/creativegroups'
11
+ end
12
+
13
+ end
@@ -0,0 +1,30 @@
1
+ module AdLeads::Etag
2
+ def etag_path
3
+ # must be implemented in including class
4
+ end
5
+
6
+ def refresh_etag!
7
+ @etag = client.get(etag_path).headers['Etag']
8
+ end
9
+
10
+ def etag
11
+ @etag ||= refresh_etag!
12
+ end
13
+
14
+ def with_etag(count = 1, &block)
15
+ raise MismatchError if count > 3
16
+
17
+ response = yield
18
+ count += 1
19
+
20
+ if response.status == 412
21
+ self.refresh_etag!
22
+ with_etag(count) do
23
+ yield
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ class MismatchError < StandardError; end
30
+ end
@@ -0,0 +1,48 @@
1
+ class AdLeads::Image < AdLeads::Base
2
+ include AdLeads::Etag
3
+ attr_accessor :creative_group_id, :ad_id
4
+
5
+ def initialize(opts)
6
+ @creative_group_id = opts[:creative_group_id]
7
+ @ad_id = opts[:ad_id]
8
+ end
9
+
10
+ def ids
11
+ { group: creative_group_id, creative: ad_id }
12
+ end
13
+
14
+ def upload!(file)
15
+ with_etag do
16
+ client.post(image_upload_path, image_upload_params(file))
17
+ end
18
+ end
19
+
20
+ def image_upload_params(file)
21
+ {
22
+ file: Faraday::UploadIO.new(file, 'image/jpeg'),
23
+ etag: etag
24
+ }
25
+ end
26
+
27
+ def image_upload_path
28
+ [
29
+ root_path,
30
+ id,
31
+ 'file'
32
+ ].join('/')
33
+ end
34
+
35
+ def etag_path
36
+ root_path + "/#{id}"
37
+ end
38
+
39
+ def root_path
40
+ [
41
+ '/creativegroups',
42
+ creative_group_id,
43
+ 'creatives',
44
+ ad_id,
45
+ 'images'
46
+ ].join('/')
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ module AdLeads
2
+ class Promotion
3
+ def self.promotion_params(deal)
4
+ {
5
+ account: {
6
+ external_id: deal.merchant_id,
7
+ dba: deal.dba,
8
+ name: deal.from_name,
9
+ email: deal.from_email,
10
+ description: deal.merchant_about,
11
+ website: deal.website,
12
+ address1: deal.location.address1,
13
+ address2: deal.location.address2,
14
+ city: deal.location.city,
15
+ state: deal.location.state,
16
+ zip: deal.location.zip,
17
+ phone: deal.location.phone,
18
+ country: deal.location.country,
19
+ facebook: deal.facebook_info,
20
+ twitter: deal.twitter_info
21
+ },
22
+ promotion: {
23
+ external_id: deal.id,
24
+ name: deal.name,
25
+ headline: deal.subject_line,
26
+ landing_page: deal.full_landing_page_preview_url,
27
+ landing_page_shortened: deal.shortened_lp_url,
28
+ spend: deal.distribution_price,
29
+ range: deal.distribution_range,
30
+ description: deal.deal_about,
31
+ kind: deal.deal_type,
32
+ state: deal.status,
33
+ time_start: deal.start_date,
34
+ time_end: deal.end_date,
35
+ }
36
+ }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ module AdLeads
2
+
3
+ class Token
4
+ attr_accessor *Configuration::VALID_CONFIG_KEYS
5
+
6
+ def initialize(options={})
7
+ merged_options = AdLeads.options.merge(options)
8
+ Configuration::VALID_CONFIG_KEYS.each do |key|
9
+ send("#{key}=", merged_options[key])
10
+ end
11
+ end
12
+
13
+ def assertion
14
+ claims = {
15
+ iss: client_id,
16
+ aud: endpoint,
17
+ prn: principle,
18
+ scope: 'campaign_read,campaign_write,reports',
19
+ exp: Time.now.utc.to_i + (5*60),
20
+ }
21
+ JWT.encode(claims, rsa_key, 'RS256')
22
+ end
23
+
24
+ def rsa_key
25
+ @rsa_key ||= OpenSSL::PKey::RSA.new(private_key)
26
+ end
27
+
28
+ def token
29
+ unless @token
30
+ response = connection.post('/oauth/token') do |conn|
31
+ conn.params = token_request_params
32
+ end
33
+ @token = JSON.parse(response.body)['access_token']
34
+ end
35
+ @token
36
+ end
37
+
38
+ def connection
39
+ @connection ||= Faraday.new(url: token_endpoint) do |faraday|
40
+ faraday.headers['Accept'] = 'application/json'
41
+ faraday.adapter :httpclient # make requests with Net::HTTP
42
+ faraday.request :url_encoded
43
+ end
44
+ end
45
+
46
+ def token_request_params
47
+ {
48
+ grant_type: 'jwt-bearer',
49
+ assertion: assertion,
50
+ }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module AdLeads
2
+ VERSION = "0.1.2"
3
+ end
data/lib/ad_leads.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'jwt'
2
+ require 'ad_leads/version'
3
+ require 'ad_leads/configuration'
4
+ require 'ad_leads/client'
5
+ require 'ad_leads/token'
6
+ require 'ad_leads/base'
7
+ require 'ad_leads/etag'
8
+ require 'ad_leads/image'
9
+ require 'ad_leads/campaign'
10
+ require 'ad_leads/ad'
11
+ require 'ad_leads/creative_group'
12
+
13
+ module AdLeads
14
+ extend Configuration
15
+ # Your code goes here...
16
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::Ad do
4
+ let(:ad) { AdLeads::Ad.new(1) }
5
+ let(:client) { ad.client }
6
+ let(:params) { {} }
7
+
8
+ it 'inherits from AdLeads::Base' do
9
+ expect(ad.class.ancestors).to include AdLeads::Base
10
+ end
11
+
12
+ describe '#create!' do
13
+ it 'posts to Ad Leads ad endpoint for creatives' do
14
+ expect(client).to receive(:post).with('/creativegroups/1/creatives', params)
15
+ ad.create!(params)
16
+ end
17
+
18
+ it 'assigns #response' do
19
+ client.stub(:post) { 'Foobar' }
20
+ ad.create!(params)
21
+ expect(ad.response).to eq 'Foobar'
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::Base do
4
+ let(:base) { AdLeads::Base.new() }
5
+
6
+ describe '#id' do
7
+ let(:response) { double(:response, body: '{ "data": [1] }') }
8
+ before { base.response = response }
9
+
10
+ it 'parses ID from JSON response body' do
11
+ expect(base.id).to eq 1
12
+ end
13
+ end
14
+
15
+ describe 'client' do
16
+ it 'returns an instance of AdLeads::Client' do
17
+ expect(base.client).to be_a AdLeads::Client
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::Campaign do
4
+ let(:campaign) { AdLeads::Campaign.new() }
5
+ let(:client) { campaign.client }
6
+ let(:params) { {} }
7
+ let(:http_success) { double(:http_success, status: 200) }
8
+
9
+ it 'inherits from AdLeads::Base' do
10
+ expect(campaign.class.ancestors).to include AdLeads::Base
11
+ end
12
+
13
+ describe '#create!' do
14
+ it 'posts to AdLeads::Campaign#root_path' do
15
+ expect(client).to receive(:post).with('/campaigns', params)
16
+ campaign.create!(params)
17
+ end
18
+
19
+ it 'assigns #response' do
20
+ client.stub(:post) { 'Foobar' }
21
+ campaign.create!(params)
22
+ expect(campaign.response).to eq 'Foobar'
23
+ end
24
+ end
25
+
26
+ describe '#update!' do
27
+ before { campaign.instance_variable_set(:@id, 1) }
28
+
29
+ it 'posts to AdLeads::Campaign#campaign_path' do
30
+ expect(client).to receive(:post).with('/campaigns/1', params)
31
+ campaign.update!(params)
32
+ end
33
+ end
34
+
35
+ describe '#verify!' do
36
+ before { campaign.instance_variable_set(:@id, 1) }
37
+
38
+ it 'sends a GET request to AdLeads::Campaign#verify_campaign_path' do
39
+ expect(client).to receive(:get).with('/campaigns/1/plan')
40
+ campaign.verify!
41
+ end
42
+ end
43
+
44
+ describe '#launch!' do
45
+ before do
46
+ campaign.stub(:id) { 1 }
47
+ campaign.stub(:etag) { 'Fake etag' }
48
+ end
49
+
50
+ it 'posts to AdLeads::Campaign#launch_campaign_path' do
51
+ expect(client).to receive(:post).with('/campaigns/1/launch', { etag: 'Fake etag'}).and_return(http_success)
52
+ campaign.launch!
53
+ end
54
+
55
+ it 'uses #with_etag block' do
56
+ client.stub(:post)
57
+ expect(campaign).to receive(:with_etag)
58
+ campaign.launch!
59
+ end
60
+ end
61
+
62
+ describe '#etag_path' do
63
+ before { campaign.instance_variable_set(:@id, 1) }
64
+
65
+ it 'is an alias for #campaign_path' do
66
+ expect(campaign.etag_path).to eq campaign.campaign_path
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'pry'
3
+
4
+ describe AdLeads::Client do
5
+ let(:client) { AdLeads::Client.new }
6
+ let(:connection) { double(:faraday_connection) }
7
+ let(:token) { '2f7622c3-da63-42e9-a3d8-4275f70f1f79' }
8
+ let(:status) { 200 }
9
+ let(:response) { double(:http_response, status: status, body: {}, headers: {}) }
10
+
11
+ before { client.stub(:token) { token } }
12
+
13
+ describe '#connection' do
14
+ let(:config_keys) { AdLeads::Configuration::VALID_CONFIG_KEYS }
15
+
16
+ it 'inherits config' do
17
+ expect(client).to respond_to(config_keys.sample)
18
+ end
19
+
20
+ it 'calls token' do
21
+ expect(client).to receive(:token).and_return('xyz')
22
+ client.send(:connection)
23
+ end
24
+ end
25
+
26
+ describe '#request' do
27
+ before do
28
+ Faraday.stub(:new) { connection }
29
+ connection.stub(:post) { response }
30
+ end
31
+
32
+ context 'status: 500' do
33
+ let(:status) { 500 }
34
+ it 'raises AdLeads::ApiError' do
35
+ expect {
36
+ client.post('foo')
37
+ }.to raise_error AdLeads::ApiError
38
+ end
39
+ end
40
+ context 'status: 401' do
41
+ let(:status) { 401 }
42
+ it 'raises AdLeads::ApiError' do
43
+ expect {
44
+ client.post('foo')
45
+ }.to raise_error AdLeads::AuthError
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,46 @@
1
+ describe 'configuration' do
2
+
3
+ let(:keys) { AdLeads::Configuration::VALID_CONFIG_KEYS }
4
+ let(:key) { keys.sample }
5
+
6
+ after { AdLeads.reset }
7
+
8
+ it 'sets config keys' do
9
+ AdLeads.configure do |config|
10
+ config.public_send("#{key}=", key)
11
+ expect(AdLeads.public_send(key)).to eq key
12
+ end
13
+ end
14
+
15
+ it 'returns default values' do
16
+ keys.each do |key|
17
+ expect(AdLeads.send(key)).to eq AdLeads::Configuration.
18
+ const_get("DEFAULT_#{key.upcase}")
19
+ end
20
+ end
21
+
22
+ describe '#client_id' do
23
+ it 'should return default key' do
24
+ expect(AdLeads.client_id).to eq AdLeads::Configuration::DEFAULT_CLIENT_ID
25
+ end
26
+ end
27
+
28
+ describe '#principle' do
29
+ it 'should return default key' do
30
+ expect(AdLeads.principle).to eq AdLeads::Configuration::DEFAULT_PRINCIPLE
31
+ end
32
+ end
33
+
34
+ describe '#format' do
35
+ it 'should return default format' do
36
+ expect(AdLeads.format).to eq AdLeads::Configuration::DEFAULT_FORMAT
37
+ end
38
+ end
39
+
40
+ describe '#user_agent' do
41
+ it 'should return default user agent' do
42
+ expect(AdLeads.user_agent).to eq AdLeads::Configuration::DEFAULT_USER_AGENT
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::CreativeGroup do
4
+ let(:creative_group) { AdLeads::CreativeGroup.new }
5
+ let(:client) { creative_group.client }
6
+ let(:params) {{}}
7
+
8
+ it 'inherits from AdLeads::Base' do
9
+ expect(creative_group.class.ancestors).to include AdLeads::Base
10
+ end
11
+
12
+ describe '#create!' do
13
+ it 'posts to /creativegroups path' do
14
+ expect(client).to receive(:post).with('/creativegroups', params)
15
+ creative_group.create!(params)
16
+ end
17
+
18
+ it 'assigns #response' do
19
+ client.stub(:post) { 'Foobar' }
20
+ creative_group.create!(params)
21
+ expect(creative_group.response).to eq 'Foobar'
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ # TODO: Extract Etag specs!
4
+
5
+ describe AdLeads::Image do
6
+ let(:image) { AdLeads::Image.new( {creative_group_id: 1, ad_id: 1} ) }
7
+ let(:client) { image.client }
8
+ let(:params) { {} }
9
+ let(:success) { double(:http_response, status: 200) }
10
+ let(:etag_mismatch) { double(:http_response, status: 412) }
11
+ let(:new_etag) { double(:http_response, status: 200, headers: { 'Etag' => '123'} ) }
12
+
13
+ it 'inherits from AdLeads::Base' do
14
+ expect(image.class.ancestors).to include AdLeads::Base
15
+ end
16
+
17
+ describe '#create!' do
18
+ it 'posts to AdLeads::Image endpoint' do
19
+ expect(client).to receive(:post).with('/creativegroups/1/creatives/1/images', params)
20
+ image.create!(params)
21
+ end
22
+
23
+ it 'assigns #response' do
24
+ client.stub(:post) { 'Foobar' }
25
+ image.create!(params)
26
+ expect(image.response).to eq 'Foobar'
27
+ end
28
+ end
29
+
30
+ describe '#refresh_etag' do
31
+ before { image.stub(:etag_path) { '/foo' } }
32
+ it 'sets @etag from response headers' do
33
+ expect(client).to receive(:get).with('/foo').and_return(new_etag)
34
+ image.refresh_etag!
35
+ expect(image.etag).to eq '123'
36
+ end
37
+ end
38
+
39
+ describe '#upload!' do
40
+ let(:file) { './spec/fixtures/test.jpg' }
41
+ before do
42
+ image.stub(:etag) { 'Fake etag' }
43
+ image.stub(:id) { 1 }
44
+ end
45
+
46
+ it 'posts to AdLeads::Image#image_upload_path' do
47
+ image.stub(:image_upload_params) {{}}
48
+ expect(client).to receive(:post).
49
+ with('/creativegroups/1/creatives/1/images/1/file', params).
50
+ and_return(success)
51
+ image.upload!(file)
52
+ end
53
+
54
+ context 'with Etag mismatch' do
55
+ it 'retries post' do
56
+ expect(client).to receive(:post).once.and_return(etag_mismatch)
57
+ expect(image).to receive(:refresh_etag!).and_return(1)
58
+ expect(client).to receive(:post).once.and_return(success)
59
+ image.upload!(file)
60
+ end
61
+
62
+ it 'raises EtagMismatchError' do
63
+ client.stub(:post) { etag_mismatch }
64
+ client.stub(:get) { new_etag }
65
+ expect {
66
+ image.upload!(file)
67
+ }.to raise_error AdLeads::Etag::MismatchError
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::Client do
4
+ let!(:client) { AdLeads::Client.new }
5
+ let(:connection) { client.connection }
6
+ let(:creative_group_id) { 12858 }
7
+ let(:token) { '99d3492c-f82e-40c1-ac12-95a3c3326edc' }
8
+ let(:file) { './spec/fixtures/test.jpg' }
9
+
10
+ before do
11
+ client.stub(:token).and_return(token)
12
+ AdLeads::Client.stub(:new) { client }
13
+ end
14
+
15
+ context 'Network Requests' do
16
+ describe 'Ad Campaign' do
17
+ xit 'uploads logo image, creates campaign using logo image, verifies and launches ad campaign' do
18
+
19
+ params = {
20
+ 'name' => 'Creative Group Name',
21
+ 'productName' => 'amazing product',
22
+ 'privacyPolicyUrl' => 'http://privacy_url'
23
+ }
24
+
25
+ creative_group = AdLeads::CreativeGroup.new
26
+ creative_group.create!(params)
27
+
28
+ params = {
29
+ 'type' => 'Mobile',
30
+ 'name' => 'Ad name',
31
+ 'headerText' => 'get your ad on this phone today',
32
+ 'bodyText' => 'this is mobile ad body copy'
33
+ }
34
+
35
+ ad = AdLeads::Ad.new(creative_group.id)
36
+ ad.create!(params)
37
+
38
+ params = { 'type' => 'LogoImage' }
39
+
40
+ image = AdLeads::Image.new( { creative_group_id: creative_group.id, ad_id: ad.id } )
41
+ image.create!(params)
42
+ image.upload!(file)
43
+
44
+ params = {
45
+ 'name' => 'Campaign name',
46
+ 'verticals' => 82,
47
+ 'offerIncentiveCategory' => 5,
48
+ 'collectedFields' => 'firstname,lastname,email,companyname',
49
+ 'budget' => 50,
50
+ 'creativeGroups' => creative_group.id
51
+ }
52
+
53
+ campaign = AdLeads::Campaign.new
54
+ campaign.create!(params)
55
+ campaign.verify!
56
+ campaign.launch!
57
+
58
+ expect(campaign.response.status).to eq(200)
59
+ expect(JSON.parse(campaign.response.body)['result']).to eq true
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe AdLeads::Token do
4
+ let(:config_keys) { AdLeads::Configuration::VALID_CONFIG_KEYS }
5
+ let(:token) { AdLeads::Token.new }
6
+ let(:private_key) { <<-PRIVATE_KEY }
7
+ -----BEGIN RSA PRIVATE KEY-----
8
+ MIICXAIBAAKBgQCOcCBGAtoZrwaN068x87WjW0x7GcYh5hXRN7I5Ib30MqT/jz9X
9
+ AzX9WTg5Cqm+2EYSxlOi+Inoqoz1fILKGuxVnC+R7bWNrRTPWzzd4mAsfUw1Sr7c
10
+ h9055f0l5BgdBZ4QAoa/9p7avZhKmX+x3pyLZ6EzMXbALoJsQGojmfzotwIDAQAB
11
+ AoGAQlNZ49/uGSmUHrSkjtkSCng3+9Z7mUtfe2W4+ruIjU6L4GiesPDQ0iEaeg1K
12
+ D5D7yEBLr8aVyR6ptqH88QlqZJzWtOwA39+9o/HyR9tl9Py7CpMYAUIqTqLHds0r
13
+ E662oal2A2EZER0iJY3qFAbW8TlGtLSFb4vwvcQmIWl2Q/kCQQDJe9DGN5bySzgD
14
+ uD8v091Q0tDDCWG/zYkTcMT2Ii2K6tqd4AlYHaJ3wtgOhvFIbOfMqyk2YBgmfRVf
15
+ DNV4OHdtAkEAtPpj0sJB/Q3tv+ftgzqGIZOygs4UtsRtqUh96nJiUsIrxT4BkD+e
16
+ LYeCkJytfHMZ3CR0ReGuRPiiAtQAZYjWMwJBAKiZtH16HRUJvojWUAG8v3EXyFu8
17
+ 6RAwdSlQb3Er7oJVvrTnucoDmmWvJU8auqOJhnstK2J2DR+AAjc0rRlZ3w0CQEmx
18
+ +Ho3TmW8iUbvK6GXcE019qgbQQYXwMwBT/zrLSykEuzTzhEuRrwlhT5b/q1BtZMW
19
+ aR6Xwr4lPNvH9o1iBk8CQHdh405KMMAZZOsXccXqpgxUl90lTpM0p+cQERLGHmtR
20
+ OAQfFNhtZpgvPM/VALKb/RfJ7dWJzT9OMnVgtDyNsD8=
21
+ -----END RSA PRIVATE KEY-----
22
+ PRIVATE_KEY
23
+ let(:rsa_key) {
24
+ OpenSSL::PKey::RSA.new private_key
25
+ }
26
+
27
+ before { token.stub(:private_key) { private_key } }
28
+
29
+ it 'inherits config' do
30
+ expect(token).to respond_to(config_keys.sample)
31
+ end
32
+
33
+ describe '#assertion' do
34
+ let(:claims) {{
35
+ iss: token.client_id,
36
+ aud: token.endpoint,
37
+ prn: token.principle,
38
+ scope: 'campaign_read,campaign_write,reports',
39
+ exp: Time.now.utc.to_i + (5*60)
40
+ }}
41
+
42
+ it 'JWT should receive encode with correct params' do
43
+ expect(JWT).to receive(:encode).with(claims, an_instance_of(OpenSSL::PKey::RSA), 'RS256')
44
+ token.assertion
45
+ end
46
+ end
47
+
48
+ describe '#rsa_key' do
49
+ it 'RSA should initialize with new file' do
50
+ expect(OpenSSL::PKey::RSA).to receive(:new).with private_key
51
+ token.rsa_key
52
+ end
53
+
54
+ it 'should memoize the result' do
55
+ token.instance_variable_set(:@rsa_key, 'Foobar')
56
+ expect(OpenSSL::PKey::RSA).not_to receive(:new).with private_key
57
+ token.rsa_key
58
+ end
59
+ end
60
+
61
+ describe '#token' do
62
+ context '@token is set' do
63
+ it 'returns @token' do
64
+ token.instance_variable_set(:@token, 'Foobar')
65
+ expect(token.token).to eq 'Foobar'
66
+ end
67
+ end
68
+
69
+ context '@token is not set' do
70
+ let(:response) { double(:response_mock, body: '{ "access_token": "Foobar" }') }
71
+
72
+ it 'connection should receive post' do
73
+ expect(token.connection).to receive(:post).with('/oauth/token') { response }
74
+ token.token
75
+ end
76
+
77
+ it 'assigns token from response' do
78
+ token.connection.stub(:post) { response }
79
+ expect(token.token).to eq 'Foobar'
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '#token_request_params' do
85
+ let(:assertion) { 'Foobar' }
86
+ before { token.stub(:assertion) { assertion } }
87
+
88
+ it 'is valid' do
89
+ expect(token.token_request_params).to eq({
90
+ grant_type: 'jwt-bearer',
91
+ assertion: assertion
92
+ })
93
+ end
94
+ end
95
+
96
+ it 'uses AdLeads::Client.connection'
97
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ describe 'Version' do
3
+ it 'is set' do
4
+ expect(AdLeads::VERSION).not_to be_nil
5
+ end
6
+
7
+ end
@@ -0,0 +1,24 @@
1
+ {
2
+ creative_group_info: {
3
+ 'name' => 'Creative Group Name',
4
+ 'productName' => promotion.name,
5
+ 'privacyPolicyUrl' => 'http://privacy_url' # ask dave
6
+ },
7
+ ad_info: {
8
+ 'type' => ad_content.kind,
9
+ 'name' => ad_content.name,
10
+ 'headerText' => ad_content.header,
11
+ 'bodyText' => ad_content.body
12
+ },
13
+ image_info: {
14
+ 'type' => image.kind
15
+ },
16
+ campaign_info: {
17
+ 'name' => ad_campaign.name,
18
+ 'verticals' => ad_campaign.verticals,
19
+ 'offerIncentiveCategory' => ad_campaign.offer_incentive_category,
20
+ 'collectedFields' => ad_campaign.collected_fields,
21
+ 'budget' => ad_campaign.spend,
22
+ 'creativeGroups' => creative_group.id
23
+ }
24
+ }
Binary file
@@ -0,0 +1,4 @@
1
+ require 'ad_leads'
2
+ require 'rspec'
3
+
4
+
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ad_leads
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Dinshaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: httpclient
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.4.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.3.4.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: jwt
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.11
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.11
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - dgobhai@constantcontact.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - ad_leads.gemspec
124
+ - lib/ad_leads.rb
125
+ - lib/ad_leads/ad.rb
126
+ - lib/ad_leads/base.rb
127
+ - lib/ad_leads/campaign.rb
128
+ - lib/ad_leads/campaign_helpers.rb
129
+ - lib/ad_leads/client.rb
130
+ - lib/ad_leads/configuration.rb
131
+ - lib/ad_leads/creative_group.rb
132
+ - lib/ad_leads/etag.rb
133
+ - lib/ad_leads/image.rb
134
+ - lib/ad_leads/promotion.rb
135
+ - lib/ad_leads/token.rb
136
+ - lib/ad_leads/version.rb
137
+ - spec/ad_leads/ad_spec.rb
138
+ - spec/ad_leads/base_spec.rb
139
+ - spec/ad_leads/campaign_spec.rb
140
+ - spec/ad_leads/client_spec.rb
141
+ - spec/ad_leads/configuration_spec.rb
142
+ - spec/ad_leads/creative_group_spec.rb
143
+ - spec/ad_leads/image_spec.rb
144
+ - spec/ad_leads/network_requests_redux_spec.rb
145
+ - spec/ad_leads/token_spec.rb
146
+ - spec/ad_leads/version_spec.rb
147
+ - spec/fixtures/ad_campaign_obj.rb
148
+ - spec/fixtures/test.jpg
149
+ - spec/spec_helper.rb
150
+ homepage: ''
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.2.0
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Ruby Wrapper for AdLeads API
174
+ test_files:
175
+ - spec/ad_leads/ad_spec.rb
176
+ - spec/ad_leads/base_spec.rb
177
+ - spec/ad_leads/campaign_spec.rb
178
+ - spec/ad_leads/client_spec.rb
179
+ - spec/ad_leads/configuration_spec.rb
180
+ - spec/ad_leads/creative_group_spec.rb
181
+ - spec/ad_leads/image_spec.rb
182
+ - spec/ad_leads/network_requests_redux_spec.rb
183
+ - spec/ad_leads/token_spec.rb
184
+ - spec/ad_leads/version_spec.rb
185
+ - spec/fixtures/ad_campaign_obj.rb
186
+ - spec/fixtures/test.jpg
187
+ - spec/spec_helper.rb