marketo2 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/marketo2.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'savon'
3
+
4
+ Savon.configure do |config|
5
+ config.log = false # disable logging
6
+ end
7
+
8
+ require File.expand_path('marketo2/client', File.dirname(__FILE__))
9
+ require File.expand_path('marketo2/authentication_header', File.dirname(__FILE__))
10
+ require File.expand_path('marketo2/enums', File.dirname(__FILE__))
11
+ require File.expand_path('marketo2/lead_key', File.dirname(__FILE__))
12
+ require File.expand_path('marketo2/lead_record', File.dirname(__FILE__))
13
+
14
+
15
+
16
+
@@ -0,0 +1,48 @@
1
+ module Rapleaf
2
+ module Marketo
3
+ # This class exists only to encapsulate the authentication header part of a soap request to marketo
4
+ # Marketo requires a somewhat complex calculation of an encrypted signature and so it seemed sensible to pull this code out here
5
+ class AuthenticationHeader
6
+ DIGEST = OpenSSL::Digest::Digest.new('sha1')
7
+
8
+ def initialize(access_key, secret_key, time = DateTime.now)
9
+ @access_key = access_key
10
+ @secret_key = secret_key
11
+ @time = time
12
+ end
13
+
14
+ public
15
+ # time should be a DateTime instance
16
+ def set_time(time)
17
+ @time = time
18
+ end
19
+
20
+ def get_mktows_user_id
21
+ @access_key
22
+ end
23
+
24
+ def get_request_signature
25
+ calculate_signature
26
+ end
27
+
28
+ def get_request_timestamp
29
+ @time.to_s
30
+ end
31
+
32
+ def to_hash
33
+ {
34
+ "mktowsUserId" => get_mktows_user_id,
35
+ "requestSignature" => get_request_signature,
36
+ "requestTimestamp" => get_request_timestamp
37
+ }
38
+ end
39
+
40
+ private
41
+ def calculate_signature
42
+ request_timestamp = get_request_timestamp
43
+ string_to_encrypt = request_timestamp + @access_key
44
+ OpenSSL::HMAC.hexdigest(DIGEST, @secret_key, string_to_encrypt)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,202 @@
1
+ require File.expand_path('authentication_header', File.dirname(__FILE__))
2
+
3
+ module Rapleaf
4
+ module Marketo
5
+ def self.new_client(access_key, secret_key, api_subdomain = '207-XLI-813', api_version = '2_2', document_version = '2_2')
6
+ client = Savon::Client.new do
7
+ wsdl.endpoint = "https://#{api_subdomain}.mktoapi.com/soap/mktows/#{api_version}"
8
+ wsdl.document = "http://app.marketo.com/soap/mktows/#{document_version}?WSDL"
9
+ http.read_timeout = 90
10
+ http.open_timeout = 90
11
+ http.headers = {"Connection" => "Keep-Alive"}
12
+ end
13
+
14
+ Client.new(client, Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key))
15
+ end
16
+
17
+ # = The client for talking to marketo
18
+ # based on the SOAP wsdl file: <i>http://app.marketo.com/soap/mktows/1_4?WSDL</i>
19
+ #
20
+ # Usage:
21
+ #
22
+ # client = Rapleaf::Marketo.new_client(<access_key>, <secret_key>, api_subdomain = 'na-i', api_version = '1_5', document_version = '1_4')
23
+ #
24
+ # == get_lead_by_email:
25
+ #
26
+ # lead_record = client.get_lead_by_email('sombody@examnple.com')
27
+ #
28
+ # puts lead_record.idnum
29
+ #
30
+ # puts lead_record.get_attribute('FirstName')
31
+ #
32
+ # puts lead_record.get_attribute('LastName')
33
+ #
34
+ # == sync_lead: (update)
35
+ #
36
+ # lead_record = client.sync_lead('example@rapleaf.com', 'Joe', 'Smith', 'Company 1', '415 911')
37
+ #
38
+ # == sync_lead_record: (update with custom fields)
39
+ #
40
+ # lead_record = Rapleaf::Marketo::LeadRecord.new('harry@rapleaf.com')
41
+ #
42
+ # lead_record.set_attribute('FirstName', 'harry')
43
+ #
44
+ # lead_record.set_attribute('LastName', 'smith')
45
+ #
46
+ # lead_record.set_attribute('Email', 'harry@somesite.com')
47
+ #
48
+ # lead_record.set_attribute('Company', 'Rapleaf')
49
+ #
50
+ # lead_record.set_attribute('MobilePhone', '123 456')
51
+ #
52
+ # response = client.sync_lead_record(lead_record)
53
+ #
54
+ # == sync_lead_record_on_id: (update with custom fields, ensuring the sync is id based)
55
+ #
56
+ # similarly, you can force a sync via id instead of email by calling client.sync_lead_record_on_id(lead_record)
57
+ #
58
+ class Client
59
+ # This constructor is used internally, create your client with *Rapleaf::Marketo.new_client(<access_key>, <secret_key>)*
60
+ def initialize(savon_client, authentication_header)
61
+ @client = savon_client
62
+ @header = authentication_header
63
+ end
64
+
65
+ public
66
+
67
+ def get_lead_by_idnum(idnum)
68
+ get_lead(LeadKey.new(LeadKeyType::IDNUM, idnum))
69
+ end
70
+
71
+
72
+ def get_lead_by_email(email)
73
+ get_lead(LeadKey.new(LeadKeyType::EMAIL, email))
74
+ end
75
+
76
+ def set_logger(logger)
77
+ @logger = logger
78
+ end
79
+
80
+ # create (if new) or update (if existing) a lead
81
+ #
82
+ # * email - email address of lead
83
+ # * first - first name of lead
84
+ # * last - surname/last name of lead
85
+ # * company - company the lead is associated with
86
+ # * mobile - mobile/cell phone number
87
+ #
88
+ # returns the LeadRecord instance on success otherwise nil
89
+ def sync_lead(email, first, last, company, mobile)
90
+ lead_record = LeadRecord.new(email)
91
+ lead_record.set_attribute('FirstName', first)
92
+ lead_record.set_attribute('LastName', last)
93
+ lead_record.set_attribute('Email', email)
94
+ lead_record.set_attribute('Company', company)
95
+ lead_record.set_attribute('MobilePhone', mobile)
96
+ sync_lead_record(lead_record)
97
+ end
98
+
99
+ def sync_lead_record(lead_record)
100
+ begin
101
+ attributes = []
102
+ lead_record.each_attribute_pair do |name, value|
103
+ attributes << {:attr_name => name, :attr_type => 'string', :attr_value => value}
104
+ end
105
+
106
+ response = send_request("ns1:paramsSyncLead", {
107
+ :return_lead => true,
108
+ :lead_record =>
109
+ {:email => lead_record.email,
110
+ :lead_attribute_list => {
111
+ :attribute => attributes}}})
112
+ return LeadRecord.from_hash(response[:success_sync_lead][:result][:lead_record])
113
+ rescue Exception => e
114
+ @logger.log(e) if @logger
115
+ return nil
116
+ end
117
+ end
118
+
119
+ def sync_lead_record_on_id(lead_record)
120
+ idnum = lead_record.idnum
121
+ raise 'lead record id not set' if idnum.nil?
122
+
123
+ begin
124
+ attributes = []
125
+ lead_record.each_attribute_pair do |name, value|
126
+ attributes << {:attr_name => name, :attr_type => 'string', :attr_value => value}
127
+ end
128
+
129
+ attributes << {:attr_name => 'Id', :attr_type => 'string', :attr_value => idnum.to_s}
130
+
131
+ response = send_request("ns1:paramsSyncLead", {
132
+ :return_lead => true,
133
+ :lead_record =>
134
+ {
135
+ :lead_attribute_list => { :attribute => attributes},
136
+ :id => idnum
137
+ }})
138
+ return LeadRecord.from_hash(response[:success_sync_lead][:result][:lead_record])
139
+ rescue Exception => e
140
+ @logger.log(e) if @logger
141
+ return nil
142
+ end
143
+ end
144
+
145
+ def add_to_list(list_key, email)
146
+ list_operation(list_key, ListOperationType::ADD_TO, email)
147
+ end
148
+
149
+ def remove_from_list(list_key, email)
150
+ list_operation(list_key, ListOperationType::REMOVE_FROM, email)
151
+ end
152
+
153
+ def is_member_of_list?(list_key, email)
154
+ list_operation(list_key, ListOperationType::IS_MEMBER_OF, email)
155
+ end
156
+
157
+ private
158
+ def list_operation(list_key, list_operation_type, email)
159
+ begin
160
+ response = send_request("ns1:paramsListOperation", {
161
+ :list_operation => list_operation_type,
162
+ :list_key => list_key,
163
+ :strict => 'false',
164
+ :list_member_list => {
165
+ :lead_key => [
166
+ {:key_type => 'EMAIL', :key_value => email}
167
+ ]
168
+ }
169
+ })
170
+ return response
171
+ rescue Exception => e
172
+ @logger.log(e) if @logger
173
+ return nil
174
+ end
175
+ end
176
+
177
+ def get_lead(lead_key)
178
+ begin
179
+ response = send_request("ns1:paramsGetLead", {:lead_key => lead_key.to_hash})
180
+ return LeadRecord.from_hash(response[:success_get_lead][:result][:lead_record_list][:lead_record])
181
+ rescue Exception => e
182
+ @logger.log(e) if @logger
183
+ return nil
184
+ end
185
+ end
186
+
187
+ def send_request(namespace, body)
188
+ @header.set_time(DateTime.now)
189
+ response = request(namespace, body, @header.to_hash)
190
+ response.to_hash
191
+ end
192
+
193
+ def request(namespace, body, header)
194
+ @client.request namespace do |soap|
195
+ soap.namespaces["xmlns:ns1"] = "http://www.marketo.com/mktows/"
196
+ soap.body = body
197
+ soap.header["ns1:AuthenticationHeader"] = header
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,23 @@
1
+ module Rapleaf
2
+ module Marketo
3
+ # Types of operations you can do on a marketo list
4
+ module ListOperationType
5
+ ADD_TO = 'ADDTOLIST'
6
+ REMOVE_FROM = 'REMOVEFROMLIST'
7
+ IS_MEMBER_OF = 'ISMEMBEROFLIST'
8
+ end
9
+
10
+ # Types of keys that can be used to look up a lead
11
+ module LeadKeyType
12
+ IDNUM = "IDNUM"
13
+ COOKIE = "COOKIE"
14
+ EMAIL = "EMAIL"
15
+ LEADOWNEREMAIL = "LEADOWNEREMAIL"
16
+ SFDCACCOUNTID = "SFDCACCOUNTID"
17
+ SFDCCONTACTID = "SFDCCONTACTID"
18
+ SFDCLEADID = "SFDCLEADID"
19
+ SFDCLEADOWNERID = "SFDCLEADOWNERID"
20
+ SFDCOPPTYID = "SFDCOPPTYID"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ module Rapleaf
2
+ module Marketo
3
+ # Encapsulates a key used to look up or describe a specific marketo lead.
4
+ class LeadKey
5
+ # - *key_type* the type of key to use see LeadKeyType
6
+ # - *key_value* normally a string value for the given type
7
+ def initialize(key_type, key_value)
8
+ @key_type = key_type
9
+ @key_value = key_value
10
+ end
11
+
12
+ # get the key type
13
+ def key_type
14
+ @key_type
15
+ end
16
+
17
+ # get the key value
18
+ def key_value
19
+ @key_value
20
+ end
21
+
22
+ # create a hash from this instance, for sending this object to marketo using savon
23
+ def to_hash
24
+ {
25
+ :key_type => @key_type,
26
+ :key_value => @key_value
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ module Rapleaf
2
+ module Marketo
3
+ # Represents a record of the data known about a lead within marketo
4
+ class LeadRecord
5
+ def initialize(email, idnum = nil)
6
+ @idnum = idnum
7
+ @attributes = {}
8
+ set_attribute('Email', email)
9
+ end
10
+
11
+ # hydrates an instance from a savon hash returned form the marketo API
12
+ def self.from_hash(savon_hash)
13
+ lead_record = LeadRecord.new(savon_hash[:email], savon_hash[:id].to_i)
14
+ savon_hash[:lead_attribute_list][:attribute].each do |attribute|
15
+ lead_record.set_attribute(attribute[:attr_name], attribute[:attr_value])
16
+ end
17
+ lead_record
18
+ end
19
+
20
+ # get the record idnum
21
+ def idnum
22
+ @idnum
23
+ end
24
+
25
+ # get the record email
26
+ def email
27
+ get_attribute('Email')
28
+ end
29
+
30
+ def attributes
31
+ @attributes
32
+ end
33
+
34
+ # update the value of the named attribute
35
+ def set_attribute(name, value)
36
+ @attributes[name] = value
37
+ end
38
+
39
+ # update the value of the named attribute, only if blank
40
+ def set_attribute_if_blank(name, value)
41
+ set_attribute(name, value) if get_attribute(name).blank?
42
+ end
43
+
44
+ # get the value for the named attribute
45
+ def get_attribute(name)
46
+ @attributes[name]
47
+ end
48
+
49
+ # will yield pairs of |attribute_name, attribute_value|
50
+ def each_attribute_pair(&block)
51
+ @attributes.each_pair do |name, value|
52
+ block.call(name, value)
53
+ end
54
+ end
55
+
56
+ def ==(other)
57
+ @attributes == other.attributes &&
58
+ @idnum == other.idnum
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,66 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ module Rapleaf
4
+ module Marketo
5
+ ACCESS_KEY = 'ACCESS_KEY'
6
+ SECRET_KEY = 'SECRET_KEY'
7
+
8
+ describe AuthenticationHeader do
9
+ it "should set mktowsUserId to access key" do
10
+ header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY)
11
+ header.get_mktows_user_id.should == ACCESS_KEY
12
+ end
13
+
14
+ it "should set requestSignature" do
15
+ header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY)
16
+ header.get_request_signature.should_not be_nil
17
+ header.get_request_signature.should_not == ''
18
+ end
19
+
20
+ it "should set requestTimestamp in correct format" do
21
+ header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY)
22
+ time = DateTime.new(1998, 1, 17, 20, 15, 1)
23
+ header.set_time(time)
24
+ header.get_request_timestamp().should == '1998-01-17T20:15:01+00:00'
25
+ end
26
+
27
+ it "should calculate encrypted signature" do
28
+ # I got this example of the marketo API docs
29
+
30
+ access_key = 'bigcorp1_461839624B16E06BA2D663'
31
+ secret_key = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66'
32
+
33
+ header = Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key)
34
+ header.set_time(DateTime.new(2010, 4, 9, 14, 4, 55, -7/24.0))
35
+
36
+ header.get_request_timestamp.should == '2010-04-09T14:04:54-07:00'
37
+ header.get_request_signature.should == 'ffbff4d4bef354807481e66dc7540f7890523a87'
38
+ end
39
+
40
+ it "should cope if no date is given" do
41
+ header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY)
42
+ expected = DateTime.now
43
+ actual = DateTime.parse(header.get_request_timestamp)
44
+
45
+ expected.year.should == actual.year
46
+ expected.hour.should == actual.hour
47
+ end
48
+
49
+ it "should to_hash correctly" do
50
+ # I got this example from the marketo API docs
51
+
52
+ access_key = 'bigcorp1_461839624B16E06BA2D663'
53
+ secret_key = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66'
54
+
55
+ header = Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key)
56
+ header.set_time(DateTime.new(2010, 4, 9, 14, 4, 55, -7/24.0))
57
+
58
+ header.to_hash.should == {
59
+ 'mktowsUserId' => header.get_mktows_user_id,
60
+ 'requestSignature' => header.get_request_signature,
61
+ 'requestTimestamp' => header.get_request_timestamp,
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,363 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ module Rapleaf
4
+ module Marketo
5
+
6
+ describe Client do
7
+ EMAIL = "some@email.com"
8
+ IDNUM = 29
9
+ FIRST = 'Joe'
10
+ LAST = 'Smith'
11
+ COMPANY = 'Rapleaf'
12
+ MOBILE = '415 123 456'
13
+ API_KEY = 'API123KEY'
14
+
15
+ context 'Exception handling' do
16
+ it "should return nil if any exception is raised on get_lead request" do
17
+ savon_client = mock('savon_client').as_null_object
18
+ authentication_header = mock('authentication_header').as_null_object
19
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
20
+ savon_client.should_receive(:request).and_raise Exception
21
+ client.get_lead_by_email(EMAIL).should be_nil
22
+ end
23
+
24
+ it "should return nil if any exception is raised on sync_lead request" do
25
+ savon_client = mock('savon_client').as_null_object
26
+ authentication_header = mock('authentication_header').as_null_object
27
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
28
+ savon_client.should_receive(:request).and_raise Exception
29
+ client.sync_lead(EMAIL, FIRST, LAST, COMPANY, MOBILE).should be_nil
30
+ end
31
+ end
32
+
33
+ context 'Client interaction' do
34
+ it "should have the correct body format on get_lead_by_idnum" do
35
+ savon_client = mock('savon_client')
36
+ authentication_header = mock('authentication_header')
37
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
38
+ response_hash = {
39
+ :success_get_lead => {
40
+ :result => {
41
+ :count => 1,
42
+ :lead_record_list => {
43
+ :lead_record => {
44
+ :email => EMAIL,
45
+ :lead_attribute_list => {
46
+ :attribute => [
47
+ {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'},
48
+ {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'},
49
+ {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'},
50
+ {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'}
51
+ ]
52
+ },
53
+ :foreign_sys_type => nil,
54
+ :foreign_sys_person_id => nil,
55
+ :id => IDNUM.to_s
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ expect_request(savon_client,
62
+ authentication_header,
63
+ equals_matcher(:lead_key => {
64
+ :key_value => IDNUM,
65
+ :key_type => LeadKeyType::IDNUM
66
+ }),
67
+ 'ns1:paramsGetLead',
68
+ response_hash)
69
+ expected_lead_record = LeadRecord.new(EMAIL, IDNUM)
70
+ expected_lead_record.set_attribute('name1', 'val1')
71
+ expected_lead_record.set_attribute('name2', 'val2')
72
+ expected_lead_record.set_attribute('name3', 'val3')
73
+ expected_lead_record.set_attribute('name4', 'val4')
74
+ client.get_lead_by_idnum(IDNUM).should == expected_lead_record
75
+ end
76
+
77
+ it "should have the correct body format on get_lead_by_email" do
78
+ savon_client = mock('savon_client')
79
+ authentication_header = mock('authentication_header')
80
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
81
+ response_hash = {
82
+ :success_get_lead => {
83
+ :result => {
84
+ :count => 1,
85
+ :lead_record_list => {
86
+ :lead_record => {
87
+ :email => EMAIL,
88
+ :lead_attribute_list => {
89
+ :attribute => [
90
+ {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'},
91
+ {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'},
92
+ {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'},
93
+ {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'}
94
+ ]
95
+ },
96
+ :foreign_sys_type => nil,
97
+ :foreign_sys_person_id => nil,
98
+ :id => IDNUM.to_s
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ expect_request(savon_client,
105
+ authentication_header,
106
+ equals_matcher({:lead_key => {
107
+ :key_value => EMAIL,
108
+ :key_type => LeadKeyType::EMAIL}}),
109
+ 'ns1:paramsGetLead',
110
+ response_hash)
111
+ expected_lead_record = LeadRecord.new(EMAIL, IDNUM)
112
+ expected_lead_record.set_attribute('name1', 'val1')
113
+ expected_lead_record.set_attribute('name2', 'val2')
114
+ expected_lead_record.set_attribute('name3', 'val3')
115
+ expected_lead_record.set_attribute('name4', 'val4')
116
+ client.get_lead_by_email(EMAIL).should == expected_lead_record
117
+ end
118
+
119
+ it "should have the correct body format on sync_lead_record" do
120
+ savon_client = mock('savon_client')
121
+ authentication_header = mock('authentication_header')
122
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
123
+ response_hash = {
124
+ :success_sync_lead => {
125
+ :result => {
126
+ :lead_id => IDNUM,
127
+ :sync_status => {
128
+ :error => nil,
129
+ :status => 'UPDATED',
130
+ :lead_id => IDNUM
131
+ },
132
+ :lead_record => {
133
+ :email => EMAIL,
134
+ :lead_attribute_list => {
135
+ :attribute => [
136
+ {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'},
137
+ {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'},
138
+ {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'},
139
+ {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'}
140
+ ]
141
+ },
142
+ :foreign_sys_type => nil,
143
+ :foreign_sys_person_id => nil,
144
+ :id => IDNUM.to_s
145
+ }
146
+ }
147
+ }
148
+ }
149
+ expect_request(savon_client,
150
+ authentication_header,
151
+ (Proc.new do |actual|
152
+ retval = true
153
+ retval = false unless actual[:return_lead]
154
+ retval = false unless actual[:lead_record][:email].equal?(EMAIL)
155
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].size == 5
156
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => EMAIL, :attr_name => "Email", :attr_type => "string"})
157
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val1", :attr_name => "name1", :attr_type => "string"})
158
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val2", :attr_name => "name2", :attr_type => "string"})
159
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val3", :attr_name => "name3", :attr_type => "string"})
160
+ retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val4", :attr_name => "name4", :attr_type => "string"})
161
+ retval.should == true
162
+ end),
163
+ 'ns1:paramsSyncLead',
164
+ response_hash)
165
+ lead_record = LeadRecord.new(EMAIL, IDNUM)
166
+ lead_record.set_attribute('name1', 'val1')
167
+ lead_record.set_attribute('name2', 'val2')
168
+ lead_record.set_attribute('name3', 'val3')
169
+ lead_record.set_attribute('name4', 'val4')
170
+
171
+ client.sync_lead_record(lead_record).should == lead_record
172
+ end
173
+
174
+ it "should have the correct body format on sync_lead" do
175
+ savon_client = mock('savon_client')
176
+ authentication_header = mock('authentication_header')
177
+ client = Rapleaf::Marketo::Client.new(savon_client, authentication_header)
178
+ response_hash = {
179
+ :success_sync_lead => {
180
+ :result => {
181
+ :lead_id => IDNUM,
182
+ :sync_status => {
183
+ :error => nil,
184
+ :status => 'UPDATED',
185
+ :lead_id => IDNUM
186
+ },
187
+ :lead_record => {
188
+ :email => EMAIL,
189
+ :lead_attribute_list => {
190
+ :attribute => [
191
+ {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'},
192
+ {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'},
193
+ {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'},
194
+ {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'}
195
+ ]
196
+ },
197
+ :foreign_sys_type => nil,
198
+ :foreign_sys_person_id => nil,
199
+ :id => IDNUM.to_s
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ expect_request(savon_client,
206
+ authentication_header,
207
+ Proc.new { |actual|
208
+ actual_attribute_list = actual[:lead_record][:lead_attribute_list][:attribute]
209
+ actual[:lead_record][:lead_attribute_list][:attribute] = nil
210
+ expected = {
211
+ :return_lead => true,
212
+ :lead_record => {
213
+ :email => "some@email.com",
214
+ :lead_attribute_list =>
215
+ {
216
+ :attribute => nil}}
217
+ }
218
+ actual.should == expected
219
+ actual_attribute_list.should =~ [
220
+ {:attr_value => FIRST,
221
+ :attr_name => "FirstName",
222
+ :attr_type => "string"},
223
+ {:attr_value => LAST,
224
+ :attr_name => "LastName",
225
+ :attr_type => "string"},
226
+ {:attr_value => EMAIL,
227
+ :attr_name =>"Email",
228
+ :attr_type => "string"},
229
+ {:attr_value => COMPANY,
230
+ :attr_name => "Company",
231
+ :attr_type => "string"},
232
+ {:attr_value => MOBILE,
233
+ :attr_name => "MobilePhone",
234
+ :attr_type => "string"}
235
+ ]
236
+ },
237
+ 'ns1:paramsSyncLead',
238
+ response_hash)
239
+ expected_lead_record = LeadRecord.new(EMAIL, IDNUM)
240
+ expected_lead_record.set_attribute('name1', 'val1')
241
+ expected_lead_record.set_attribute('name2', 'val2')
242
+ expected_lead_record.set_attribute('name3', 'val3')
243
+ expected_lead_record.set_attribute('name4', 'val4')
244
+ client.sync_lead(EMAIL, FIRST, LAST, COMPANY, MOBILE).should == expected_lead_record
245
+ end
246
+
247
+ context "list operations" do
248
+ LIST_KEY = 'awesome leads list'
249
+
250
+ before(:each) do
251
+ @savon_client = mock('savon_client')
252
+ @authentication_header = mock('authentication_header')
253
+ @client = Rapleaf::Marketo::Client.new(@savon_client, @authentication_header)
254
+ end
255
+
256
+ it "should have the correct body format on add_to_list" do
257
+ response_hash = {} # TODO
258
+ expect_request(@savon_client,
259
+ @authentication_header,
260
+ equals_matcher({
261
+ :list_operation => ListOperationType::ADD_TO,
262
+ :list_key => LIST_KEY,
263
+ :strict => 'false',
264
+ :list_member_list => {
265
+ :lead_key => [
266
+ {
267
+ :key_type => 'EMAIL',
268
+ :key_value => EMAIL
269
+ }
270
+ ]
271
+ }
272
+ }),
273
+ 'ns1:paramsListOperation',
274
+ response_hash)
275
+
276
+ @client.add_to_list(LIST_KEY, EMAIL).should == response_hash
277
+ end
278
+
279
+ it "should have the correct body format on remove_from_list" do
280
+ response_hash = {} # TODO
281
+ expect_request(@savon_client,
282
+ @authentication_header,
283
+ equals_matcher({
284
+ :list_operation => ListOperationType::REMOVE_FROM,
285
+ :list_key => LIST_KEY,
286
+ :strict => 'false',
287
+ :list_member_list => {
288
+ :lead_key => [
289
+ {
290
+ :key_type => 'EMAIL',
291
+ :key_value => EMAIL
292
+ }
293
+ ]
294
+ }
295
+ }),
296
+ 'ns1:paramsListOperation',
297
+ response_hash)
298
+
299
+ @client.remove_from_list(LIST_KEY, EMAIL).should == response_hash
300
+ end
301
+
302
+ it "should have the correct body format on is_member_of_list?" do
303
+ response_hash = {} # TODO
304
+ expect_request(@savon_client,
305
+ @authentication_header,
306
+ equals_matcher({
307
+ :list_operation => ListOperationType::IS_MEMBER_OF,
308
+ :list_key => LIST_KEY,
309
+ :strict => 'false',
310
+ :list_member_list => {
311
+ :lead_key => [
312
+ {
313
+ :key_type => 'EMAIL',
314
+ :key_value => EMAIL
315
+ }
316
+ ]
317
+ }
318
+ }),
319
+ 'ns1:paramsListOperation',
320
+ response_hash)
321
+
322
+ @client.is_member_of_list?(LIST_KEY, EMAIL).should == response_hash
323
+ end
324
+ end
325
+ end
326
+
327
+ private
328
+
329
+ def equals_matcher(expected)
330
+ Proc.new { |actual|
331
+ actual.should == expected
332
+ }
333
+ end
334
+
335
+ def expect_request(savon_client, authentication_header, expected_body_matcher, expected_namespace, response_hash)
336
+ header_hash = stub('header_hash')
337
+ soap_response = stub('soap_response')
338
+ request_namespace = mock('namespace')
339
+ request_header = mock('request_header')
340
+ soap_request = mock('soap_request')
341
+ authentication_header.should_receive(:set_time)
342
+ authentication_header.should_receive(:to_hash).and_return(header_hash)
343
+ request_namespace.should_receive(:[]=).with("xmlns:ns1", "http://www.marketo.com/mktows/")
344
+ request_header.should_receive(:[]=).with("ns1:AuthenticationHeader", header_hash)
345
+ soap_request.should_receive(:namespaces).and_return(request_namespace)
346
+ soap_request.should_receive(:header).and_return(request_header)
347
+ soap_request.should_receive(:body=) do |actual_body|
348
+ expected_body_matcher.call(actual_body)
349
+ end
350
+ soap_response.should_receive(:to_hash).and_return(response_hash)
351
+ savon_client.should_receive(:request).with(expected_namespace).and_yield(soap_request).and_return(soap_response)
352
+ end
353
+ end
354
+
355
+ describe ListOperationType do
356
+ it 'should define the correct types' do
357
+ ListOperationType::ADD_TO.should == 'ADDTOLIST'
358
+ ListOperationType::IS_MEMBER_OF.should == 'ISMEMBEROFLIST'
359
+ ListOperationType::REMOVE_FROM.should == 'REMOVEFROMLIST'
360
+ end
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ module Rapleaf
4
+ module Marketo
5
+ describe LeadKeyType do
6
+ it "should define the correct types" do
7
+ LeadKeyType::IDNUM.should == 'IDNUM'
8
+ LeadKeyType::COOKIE.should == 'COOKIE'
9
+ LeadKeyType::EMAIL.should == 'EMAIL'
10
+ LeadKeyType::LEADOWNEREMAIL.should == 'LEADOWNEREMAIL'
11
+ LeadKeyType::SFDCACCOUNTID.should == 'SFDCACCOUNTID'
12
+ LeadKeyType::SFDCCONTACTID.should == 'SFDCCONTACTID'
13
+ LeadKeyType::SFDCLEADID.should == 'SFDCLEADID'
14
+ LeadKeyType::SFDCLEADOWNERID.should == 'SFDCLEADOWNERID'
15
+ LeadKeyType::SFDCOPPTYID.should == 'SFDCOPPTYID'
16
+ end
17
+ end
18
+
19
+ describe LeadKey do
20
+ it "should store type and value on construction" do
21
+ KEY_VALUE = 'a value'
22
+ KEY_TYPE = LeadKeyType::IDNUM
23
+ lead_key = LeadKey.new(KEY_TYPE, KEY_VALUE)
24
+ lead_key.key_type.should == KEY_TYPE
25
+ lead_key.key_value.should == KEY_VALUE
26
+ end
27
+
28
+ it "should to_hash correctly" do
29
+ KEY_VALUE = 'a value'
30
+ KEY_TYPE = LeadKeyType::IDNUM
31
+ lead_key = LeadKey.new(KEY_TYPE, KEY_VALUE)
32
+
33
+ lead_key.to_hash.should == {
34
+ :key_type => KEY_TYPE,
35
+ :key_value => KEY_VALUE
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ module Rapleaf
4
+ module Marketo
5
+ EMAIL = 'some@email.com'
6
+ IDNUM = 93480938
7
+
8
+ describe LeadRecord do
9
+ it "should store the idnum" do
10
+ lead_record = LeadRecord.new(EMAIL, IDNUM)
11
+ lead_record.idnum.should == IDNUM
12
+ end
13
+
14
+ it "should store the email" do
15
+ LeadRecord.new(EMAIL, IDNUM).email.should == EMAIL
16
+ end
17
+
18
+ it "should implement == sensibly" do
19
+ lead_record1 = LeadRecord.new(EMAIL, IDNUM)
20
+ lead_record1.set_attribute('favourite color', 'red')
21
+ lead_record1.set_attribute('age', '100')
22
+
23
+ lead_record2 = LeadRecord.new(EMAIL, IDNUM)
24
+ lead_record2.set_attribute('favourite color', 'red')
25
+ lead_record2.set_attribute('age', '100')
26
+
27
+ lead_record1.should == lead_record2
28
+ end
29
+
30
+ it "should store when attributes are set" do
31
+ lead_record = LeadRecord.new(EMAIL, IDNUM)
32
+ lead_record.set_attribute('favourite color', 'red')
33
+ lead_record.get_attribute('favourite color').should == 'red'
34
+ end
35
+
36
+ it "should store when attributes are updated" do
37
+ lead_record = LeadRecord.new(EMAIL, IDNUM)
38
+ lead_record.set_attribute('favourite color', 'red')
39
+ lead_record.set_attribute('favourite color', 'green')
40
+ lead_record.get_attribute('favourite color').should == 'green'
41
+ end
42
+
43
+ it "should yield all attributes through each_attribute_pair" do
44
+ lead_record = LeadRecord.new(EMAIL, IDNUM)
45
+ lead_record.set_attribute('favourite color', 'red')
46
+ lead_record.set_attribute('favourite color', 'green')
47
+ lead_record.set_attribute('age', '99')
48
+
49
+ pairs = []
50
+ lead_record.each_attribute_pair do |attribute_name, attribute_value|
51
+ pairs << [attribute_name, attribute_value]
52
+ end
53
+
54
+ pairs.size.should == 3
55
+ pairs.should include(['favourite color', 'green'])
56
+ pairs.should include(['age', '99'])
57
+ pairs.should include(['Email', EMAIL])
58
+ end
59
+
60
+ it "should be instantiable from a savon hash" do
61
+ savon_hash = {
62
+ :email => EMAIL,
63
+ :foreign_sys_type => nil,
64
+ :lead_attribute_list => {
65
+ :attribute => [
66
+ { :attr_name => 'Company', :attr_type => 'string', :attr_value => 'Rapleaf'},
67
+ { :attr_name => 'FirstName', :attr_type => 'string', :attr_value => 'James'},
68
+ { :attr_name => 'LastName', :attr_type => 'string', :attr_value => 'O\'Brien'}
69
+ ]
70
+ },
71
+ :foreign_sys_person_id => nil,
72
+ :id => IDNUM
73
+ }
74
+
75
+ actual = LeadRecord.from_hash(savon_hash)
76
+
77
+ expected = LeadRecord.new(EMAIL, IDNUM)
78
+ expected.set_attribute('Company', 'Rapleaf')
79
+ expected.set_attribute('FirstName', 'James')
80
+ expected.set_attribute('LastName', 'O\'Brien')
81
+
82
+ actual.should == expected
83
+ end
84
+ end
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: marketo2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.4.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Maxime Domain
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-09 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70357517664720 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.3.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70357517664720
25
+ - !ruby/object:Gem::Dependency
26
+ name: savon
27
+ requirement: &70357517664120 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70357517664120
36
+ description: ! " Allows easy integration with marketo from ruby (v2 and up). You
37
+ can synchronize leads and fetch them back by email.\n By default this is configured
38
+ for the SOAP wsdl file: http://app.marketo.com/soap/mktows/1_4?WSDL but this is\n
39
+ \ configurable when you construct the client, e.g.\n client = Rapleaf::Marketo.new_client(<access_key>,
40
+ <secret_key>, (api_subdomain = 'na-i'), (api_version = '1_5'), (document_version
41
+ = '1_4'))\n More information at https://www.rapleaf.com/developers/marketo.\n
42
+ \ Forked from James O'Brien's marketo gem.\n"
43
+ email: hanaiapa@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/marketo2/authentication_header.rb
49
+ - lib/marketo2/client.rb
50
+ - lib/marketo2/enums.rb
51
+ - lib/marketo2/lead_key.rb
52
+ - lib/marketo2/lead_record.rb
53
+ - lib/marketo2.rb
54
+ - spec/marketo2/authentication_header_spec.rb
55
+ - spec/marketo2/client_spec.rb
56
+ - spec/marketo2/lead_key_spec.rb
57
+ - spec/marketo2/lead_record_spec.rb
58
+ homepage: https://github.com/hanaiapa/marketo2
59
+ licenses: []
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --title
63
+ - Marketo Client Gem
64
+ - --main
65
+ - Rapleaf::Marketo::Client
66
+ require_paths:
67
+ - - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.10
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: A client for the marketo API
86
+ test_files:
87
+ - spec/marketo2/authentication_header_spec.rb
88
+ - spec/marketo2/client_spec.rb
89
+ - spec/marketo2/lead_key_spec.rb
90
+ - spec/marketo2/lead_record_spec.rb