leadtune 0.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.
@@ -0,0 +1,98 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ require "json"
8
+
9
+ module Leadtune
10
+ class Rest #:nodoc:all
11
+
12
+ def initialize(config)
13
+ @config = config
14
+ @post_data = nil
15
+ end
16
+
17
+ def get(post_data)
18
+ @post_data = post_data
19
+ curl = build_curl_easy_object_get
20
+ curl.http("GET")
21
+ curl.body_str ? JSON::parse(curl.body_str) : {}
22
+ end
23
+
24
+ def post(post_data)
25
+ @post_data = post_data
26
+ curl = build_curl_easy_object_post
27
+ curl.http("POST")
28
+ curl.body_str ? JSON::parse(curl.body_str) : {}
29
+ end
30
+
31
+ def put(post_data)
32
+ @post_data = post_data
33
+ curl = build_curl_easy_object_put
34
+ curl.http("PUT")
35
+ curl.body_str ? JSON::parse(curl.body_str) : {}
36
+ end
37
+
38
+
39
+ private
40
+
41
+ def build_curl_easy_object(&block) #:nodoc:
42
+ Curl::Easy.new do |curl|
43
+ curl.http_auth_types = [:basic,]
44
+ curl.username = @config.username
45
+ curl.password = @config.password
46
+ curl.timeout = @config.timeout
47
+ curl.headers = default_headers
48
+ curl.on_failure do |curl, code|
49
+ raise LeadtuneError.new("#{curl.response_code} #{curl.body_str}")
50
+ end
51
+ #curl.verbose = true
52
+ yield curl
53
+ end
54
+ end
55
+
56
+ def default_headers #:nodoc:
57
+ {"Content-Type" => "application/json",
58
+ "Accept" => "application/json",}
59
+ end
60
+
61
+ def build_curl_easy_object_post #:nodoc:
62
+ build_curl_easy_object do |curl|
63
+ curl.url = URI.join(@config.leadtune_host, "/prospects").to_s
64
+ curl.post_body = @post_data.to_json
65
+ end
66
+ end
67
+
68
+ def build_curl_easy_object_put #:nodoc:
69
+ build_curl_easy_object do |curl|
70
+ curl.url = build_put_url
71
+ #curl.verbose = true
72
+ curl.put_data = @post_data.to_json
73
+ end
74
+ end
75
+
76
+ def build_curl_easy_object_get #:nodoc:
77
+ build_curl_easy_object do |curl|
78
+ curl.url = build_get_url
79
+ end
80
+ end
81
+
82
+ def build_get_url #:nodoc:
83
+ params = {:organization => @post_data["organization"],}
84
+ if @post_data["prospect_ref"]
85
+ params.merge!(:prospect_ref => @post_data["prospect_ref"])
86
+ end
87
+
88
+ URI.join(build_put_url, "?" + params.to_params).to_s
89
+ end
90
+
91
+ def build_put_url #:nodoc:
92
+ path = "/prospects"
93
+ path += "/#{@post_data["prospect_id"]}" if @post_data["prospect_id"]
94
+ URI.join(@config.leadtune_host, path).to_s
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ module Leadtune
8
+ VERSION = "0.0.1"
9
+ end
data/lib/leadtune.rb ADDED
@@ -0,0 +1,26 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ dir = File.dirname(__FILE__)
8
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
9
+
10
+ require "array_extensions"
11
+ require "hash_extensions"
12
+
13
+
14
+ # For details about the LeadTune API, see: http://leadtune.com/api
15
+
16
+ module Leadtune #:nodoc:all
17
+ end
18
+
19
+ # Raised when non-2XX responses are received.
20
+ class Leadtune::LeadtuneError < RuntimeError ; end
21
+
22
+
23
+ require "leadtune/prospect"
24
+ require "leadtune/rest"
25
+ require "leadtune/appraisals"
26
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # LeadTune API Ruby Gem
4
+ #
5
+ # http://github.com/leadtune/leadtune-ruby
6
+ # Eric Wollesen (mailto:devs@leadtune.com)
7
+ # Copyright 2010 LeadTune LLC
8
+
9
+ require "rubygems"
10
+ require "ruby-debug"
11
+ require "webrick"
12
+ require "json"
13
+ require "pp"
14
+
15
+ server = WEBrick::HTTPServer.new({:Port => 8080})
16
+ trap("INT"){server.shutdown}
17
+ trap("TERM"){server.shutdown}
18
+
19
+ def random_buyers(target_buyers)
20
+ target_buyers.map {|name| {"target_buyer" => name, :value => [0, 1].choice}}
21
+ end
22
+
23
+ server.mount_proc("/prospects") do |request, response|
24
+ if request.body
25
+ json = JSON.parse(request.body) if request.body
26
+
27
+ pp(json)
28
+
29
+ if json.include?("sleep")
30
+ $stderr.puts "sleeping..."
31
+ sleep json["sleep"].to_i
32
+ end
33
+
34
+ response.status = 201
35
+ response["Content-Type"] = "application/json"
36
+ r = {
37
+ "prospect_id" => "deadbeef",
38
+ "decision" => {
39
+ "decision_id" => "deadbeef",
40
+ "organization" => json["organization"],
41
+ "created_at" => Time.now,
42
+ "appraisals" => random_buyers(json["decision"]["target_buyers"]),
43
+ },
44
+ "organization" => json["organization"],
45
+ "event" => json["event"],
46
+ "email_hash" => "deadbeef",
47
+ "created_at" => Time.now,
48
+ }
49
+ response.body = r.to_json
50
+ else
51
+ response.body = {:prospect_ids => []}.to_json
52
+ end
53
+ end
54
+
55
+ server.start
data/spec/get.rb ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # LeadTune API Ruby Gem
4
+ #
5
+ # http://github.com/leadtune/leadtune-ruby
6
+ # Eric Wollesen (mailto:devs@leadtune.com)
7
+ # Copyright 2010 LeadTune LLC
8
+
9
+ require "rubygems"
10
+ require "pp"
11
+ require File.join(File.dirname(__FILE__), "../lib/leadtune")
12
+
13
+
14
+ begin
15
+ p = Leadtune::Prospect.new do |p|
16
+ p.organization = "AcmeU"
17
+ p.username = "admin@acme.edu"
18
+ p.password = "admin"
19
+ # p.leadtune_host = "http://localhost:8080"
20
+ p.prospect_ref = "CRONIN"
21
+ end
22
+
23
+ p.get
24
+ pp p.factors
25
+
26
+ pp Leadtune::Prospect.new({:prospect_id => "4c92f8d6b34601dd5ecac030",
27
+ :username => "admin@acme.edu",
28
+ :password => "admin",
29
+ :organization => "AcmeU",}).get.factors
30
+ rescue Leadtune::LeadtuneError => e
31
+ puts e.to_s
32
+ end
33
+
@@ -0,0 +1,26 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ require "spec_helper"
8
+
9
+ describe Leadtune::Appraisals do
10
+
11
+ subject do
12
+ Leadtune::Appraisals.new([{"target_buyer" => "AcmeU", "value" => 1},
13
+ {"target_buyer" => "Bravo", "value" => 0},])
14
+ end
15
+
16
+ describe("#non_duplicates") do
17
+ it "includes target buyers to whom the prospect is not a duplicate" do
18
+ subject.non_duplicates.should include({"target_buyer" => "AcmeU", "value" => 1})
19
+ end
20
+
21
+ it "excludes target buyers to whom the prospect is a duplicate" do
22
+ subject.non_duplicates.should_not include({"target_buyer" => "Bravo", "value" => 0})
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,162 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ require "digest"
8
+ require "json"
9
+ require "spec_helper"
10
+
11
+ describe Leadtune::Prospect do
12
+
13
+ subject do
14
+ Leadtune::Prospect.new({"prospect_id" => "deadfish",
15
+ "email" => "bar@baz.com",
16
+ "target_buyers" => ["AcmeU", "Bravo", "ConvU",],
17
+ "event" => "offers_prepared",}) do |p|
18
+ # use ||= so we won't override if loaded from ENV or config_file
19
+ p.organization ||= "Foo"
20
+ end
21
+ end
22
+
23
+ context("w/ organization from config_file") do
24
+ subject do
25
+ Leadtune::Prospect.new(leadtune_config_file)
26
+ end
27
+
28
+ describe "#organization" do
29
+ specify {subject.organization.should == "config_file_org"}
30
+ end
31
+ end
32
+
33
+ context("when presented with an unrecognized factor") do
34
+ it "creates a setter and a getter by that name" do
35
+ fail "getter already exists" if subject.respond_to?(:my_new_factor)
36
+ fail "setter already exists" if subject.respond_to?(:my_new_factor=)
37
+
38
+ subject.my_new_factor = 5
39
+
40
+ subject.should respond_to(:my_new_factor=)
41
+ subject.should respond_to(:my_new_factor)
42
+ subject.my_new_factor.should == 5
43
+ end
44
+ end
45
+
46
+ context("w/ organization from ENV") do
47
+ before(:all) do
48
+ setup_leadtune_env
49
+ end
50
+
51
+ after(:all) do
52
+ teardown_leadtune_env
53
+ end
54
+
55
+ subject {Leadtune::Prospect.new}
56
+
57
+ describe "#organization" do
58
+ specify {subject.organization.should == "env_org"}
59
+ end
60
+ end
61
+
62
+ context("w/ organization from ENV *AND* config_file") do
63
+
64
+ before(:all) do
65
+ setup_leadtune_env
66
+ end
67
+
68
+ after(:all) do
69
+ teardown_leadtune_env
70
+ end
71
+
72
+ subject {Leadtune::Prospect.new(leadtune_config_file)}
73
+
74
+ describe "#organization" do
75
+ it "uses the ENV value over the config file" do
76
+ subject.organization.should == "env_org"
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#get" do
82
+ before(:each) do
83
+ stub_request(:any, /.*leadtune.*/).to_return(:body => fake_curb_response)
84
+ end
85
+
86
+ it "loads the browser_family factor" do
87
+ subject.get
88
+
89
+ subject.browser_family.should == "Firefox"
90
+ end
91
+
92
+ end
93
+
94
+ describe "#post_body" do
95
+ before(:each) do
96
+ # requests are stubbed by json_factors_should_include
97
+ end
98
+
99
+ it "includes decision" do
100
+ rest = double(Leadtune::Rest).as_null_object
101
+ Leadtune::Rest.stub!(:new).and_return(rest)
102
+ expected_factors = {"decision" => subject.decision,}
103
+ subject.stub(:parse_response)
104
+
105
+ rest.should_receive(:post).with {|hash| hash.should include(expected_factors)}
106
+
107
+ subject.post
108
+ end
109
+ end
110
+
111
+ describe("#new") do
112
+ it "receives options in a Hash" do
113
+ s = Leadtune::Prospect.new({:channel => "banner",})
114
+
115
+ s.channel.should == "banner"
116
+ end
117
+
118
+ it "accepts a config_file as its (optional) first argument" do
119
+ s = Leadtune::Prospect.new(leadtune_config_file, {:channel => "banner",})
120
+
121
+ s.channel.should == "banner"
122
+ s.organization.should == "config_file_org"
123
+ end
124
+ end
125
+
126
+ describe("#target_buyers=") do
127
+ it "is represented in the decision field of the JSON post body" do
128
+ subject.target_buyers = ["foo",]
129
+ json_factors_should_include({"decision" => {"target_buyers" => ["foo"]}})
130
+
131
+ subject.post
132
+ end
133
+
134
+ it "raises ArgumentError when called with a non-Array" do
135
+ lambda {subject.target_buyers = "foo"}.should raise_error(ArgumentError)
136
+ end
137
+ end
138
+
139
+
140
+ private
141
+
142
+ def json_factors_should_include(expected_factors)
143
+ stub_request(:any, /.*leadtune.*/).to_return do |req|
144
+ json = JSON::parse(req.body)
145
+ json.should include(expected_factors)
146
+ {:body => req.body, :status => [201, "Created"]}
147
+ end
148
+ end
149
+
150
+ def fake_curb_response
151
+ {:event => "offers_prepared",
152
+ :organization => "LOL",
153
+ :created_at => Time.now,
154
+ :browser_family => "Firefox",
155
+ :browser_version => "3.6.3",
156
+ :email_hash => "deadbeef",
157
+ :decision => {
158
+ :appraisals => [{:target_buyer => "TB-LOL", :value => 1},],
159
+ },
160
+ :prospect_id => "deadbeef",}.to_json
161
+ end
162
+ end
@@ -0,0 +1,243 @@
1
+ # LeadTune API Ruby Gem
2
+ #
3
+ # http://github.com/leadtune/leadtune-ruby
4
+ # Eric Wollesen (mailto:devs@leadtune.com)
5
+ # Copyright 2010 LeadTune LLC
6
+
7
+ require "tempfile"
8
+ require "webrick"
9
+ require "tcpsocket-wait"
10
+
11
+ require "spec_helper"
12
+
13
+ describe Leadtune::Rest do
14
+
15
+ subject {Leadtune::Rest.new(Leadtune::Config.new)}
16
+
17
+ context("w/ username & password from config_file") do
18
+
19
+ subject {Leadtune::Rest.new(rest_config)}
20
+
21
+ before(:each) do
22
+ @curl_easy = null_curl_easy
23
+ end
24
+
25
+ describe "#username" do
26
+ it "uses the config_file value" do
27
+ @curl_easy.should_receive(:username=).with("config@config.com")
28
+
29
+ subject.get(mock_post_data)
30
+ end
31
+ end
32
+
33
+ describe "#password" do
34
+ it "uses the config_file value" do
35
+ @curl_easy.should_receive(:password=).with("config_secret")
36
+
37
+ subject.get(mock_post_data)
38
+ end
39
+ end
40
+ end
41
+
42
+ context("w/ username & password from ENV") do
43
+ before(:all) do
44
+ setup_leadtune_env
45
+ end
46
+
47
+ after(:all) do
48
+ teardown_leadtune_env
49
+ end
50
+
51
+ before(:each) do
52
+ @curl_easy = null_curl_easy
53
+ end
54
+
55
+ describe "#username" do
56
+ it "uses the ENV value" do
57
+ @curl_easy.should_receive(:username=).with("env@env.com")
58
+
59
+ subject.get(mock_post_data)
60
+ end
61
+ end
62
+
63
+ describe "#password" do
64
+ it "uses the ENV value" do
65
+ @curl_easy.should_receive(:password=).with("env_secret")
66
+
67
+ subject.get(mock_post_data)
68
+ end
69
+ end
70
+ end
71
+
72
+ context("w/ username & password from ENV *AND* config_file") do
73
+
74
+ subject {Leadtune::Rest.new(rest_config)}
75
+
76
+ before(:all) do
77
+ setup_leadtune_env
78
+ end
79
+
80
+ after(:all) do
81
+ teardown_leadtune_env
82
+ end
83
+
84
+ before(:each) do
85
+ @curl_easy = null_curl_easy
86
+ end
87
+
88
+ describe "#username" do
89
+ it "uses the ENV value over the config file" do
90
+ @curl_easy.should_receive(:username=).with("env@env.com")
91
+
92
+ subject.get(mock_post_data)
93
+ end
94
+ end
95
+
96
+ describe "#password" do
97
+ it "uses the ENV value over the config file" do
98
+ @curl_easy.should_receive(:password=).with("env_secret")
99
+
100
+ subject.get(mock_post_data)
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#post (slow)" do
106
+
107
+ before(:all) {WebMock.allow_net_connect!}
108
+ after(:all) {WebMock.disable_net_connect!}
109
+
110
+ ["401", "404", "500"].each do |code|
111
+ context("when a #{code} is returned") do
112
+ it "raises a LeadtuneError" do
113
+ mock_server(code) do
114
+ lambda {subject.post(mock_post_data)}.should raise_error(Leadtune::LeadtuneError)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ describe("#timeout") do
122
+ before(:each) do
123
+ @curl_easy = null_curl_easy
124
+ end
125
+
126
+ it "is passed on to Curl::Easy" do
127
+ @curl_easy.should_receive(:timeout=).with(5)
128
+
129
+ subject.get(mock_post_data)
130
+ end
131
+
132
+ context("by default") do
133
+ it "is 5" do
134
+ @curl_easy.should_receive(:timeout=).with(5)
135
+
136
+ subject.get(mock_post_data)
137
+ end
138
+ end
139
+
140
+ context("with timeout of 6 in ENV value") do
141
+ before(:all) do
142
+ ENV["LEADTUNE_TIMEOUT"] = "6"
143
+ end
144
+
145
+ after(:all) do
146
+ ENV.delete("LEADTUNE_TIMEOUT")
147
+ end
148
+
149
+ it "is 6" do
150
+ @curl_easy.should_receive(:timeout=).with(6)
151
+
152
+ subject.get(mock_post_data)
153
+ end
154
+ end
155
+
156
+ context("with timeout of 7 in config_file") do
157
+ subject {Leadtune::Rest.new(rest_config)}
158
+
159
+ it "is 7" do
160
+ @curl_easy.should_receive(:timeout=).with(7)
161
+
162
+ subject.get(mock_post_data)
163
+ end
164
+ end
165
+
166
+ end
167
+
168
+
169
+ private
170
+
171
+ def json_factors_should_include(expected_factors)
172
+ stub_request(:any, /.*leadtune.*/).to_return do |req|
173
+ json = JSON::parse(req.body)
174
+ json.should include(expected_factors)
175
+ {:body => req.body, :status => [201, "Created"]}
176
+ end
177
+ end
178
+
179
+ def fake_curb_response
180
+ {:event => "offers_prepared",
181
+ :organization => "LOL",
182
+ :created_at => Time.now,
183
+ :browser_family => "Firefox",
184
+ :browser_version => "3.6.3",
185
+ :email_hash => "deadbeef",
186
+ :decision => {
187
+ :appraisals => [{:target_buyer => "TB-LOL", :value => 1},],
188
+ },
189
+ :prospect_id => "deadbeef",}.to_json
190
+ end
191
+
192
+ def mock_post_data
193
+ {"prospect_id" => "deadbeef",}
194
+ end
195
+
196
+ def null_curl_easy
197
+ curl_easy = double(Curl::Easy, :body_str => "{}").as_null_object
198
+ Curl::Easy.stub!(:new).and_yield(curl_easy).and_return(curl_easy)
199
+ curl_easy
200
+ end
201
+
202
+ def rest_config
203
+ config_file = StringIO.new <<EOF
204
+ username: config@config.com
205
+ password: config_secret
206
+ timeout: 7
207
+ EOF
208
+ Leadtune::Config.new(config_file)
209
+ end
210
+
211
+ def mock_server(code, &block)
212
+ quietly do
213
+ server = WEBrick::HTTPServer.new(:Port => THREADED_MOCK_SERVER_PORT)
214
+ server.mount_proc("/prospects") do |req, res|
215
+ res.body = "mock_server"
216
+ res.status = code
217
+ end
218
+
219
+ thread = Thread.new(server) {|s| s.start}
220
+
221
+ TCPSocket.wait_for_service_with_timeout({:host => "localhost",
222
+ :port => THREADED_MOCK_SERVER_PORT,
223
+ :timeout => 10})
224
+ block.call
225
+ server.shutdown
226
+ thread.join
227
+ end
228
+ end
229
+
230
+ def quietly(&block)
231
+ old_stdout = old_stderr = nil
232
+
233
+ Tempfile.open("seller_spec") do |tf|
234
+ old_stdout, $stdout = $stdout, tf
235
+ old_stderr, $stderr = $stderr, tf
236
+ block.call(tf)
237
+ end
238
+
239
+ $stdout, $stderr = old_stdout, old_stderr
240
+ end
241
+
242
+ THREADED_MOCK_SERVER_PORT = 9292
243
+ end
data/spec/post.rb ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # LeadTune API Ruby Gem
4
+ #
5
+ # http://github.com/leadtune/leadtune-ruby
6
+ # Eric Wollesen (mailto:devs@leadtune.com)
7
+ # Copyright 2010 LeadTune LLC
8
+
9
+ require "rubygems"
10
+ require "ruby-debug"
11
+ require "pp"
12
+ require File.join(File.dirname(__FILE__), "../lib/leadtune")
13
+
14
+ p = Leadtune::Prospect.post do |p|
15
+ p.event = "offers_prepared"
16
+ p.organization = "LOL"
17
+ p.username = "admin@loleads.com"
18
+ p.password = "admin"
19
+ p.email = "test@example.com"
20
+ p.target_buyers = ["AcmeU", "Bravo", "ConvU",]
21
+ #p.leadtune_host = "http://localhost:8080"
22
+ end
23
+
24
+ pp p.factors
25
+ pp p.decision_id