marketo2 1.4.0

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.
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