leadtune 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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