marketo-api-ruby 0.8

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,115 @@
1
+ require_relative 'client_proxy'
2
+ require_relative 'mobject'
3
+
4
+ # MarketoAPI operations on Marketo objects (MObject).
5
+ class MarketoAPI::MObjects < MarketoAPI::ClientProxy
6
+
7
+ # :call-seq:
8
+ # delete(mobject, mobject, ...) -> status_list
9
+ #
10
+ # Implements
11
+ # {+deleteMObjects+}[http://developers.marketo.com/documentation/soap/deletemobjects/],
12
+ # returning the deletion success status for each object provided.
13
+ #
14
+ # Only works with Opportunity or OpportunityPersonRole MObjects.
15
+ #
16
+ # To delete an Opportunity:
17
+ #
18
+ # marketo.mobjects.delete MarketoAPI::MObject.opportunity(75)
19
+ def delete(*mobjects)
20
+ if mobjects.empty?
21
+ raise ArgumentError, "must provide one or more MObjects to delete"
22
+ end
23
+ response = call(
24
+ :delete_m_objects,
25
+ m_object_list: transform_param_list(__method__, mobjects)
26
+ )
27
+ extract_mobject_status_list(response)
28
+ end
29
+
30
+ # Implements
31
+ # {+listMObjects+}[http://developers.marketo.com/documentation/soap/listmobjects/],
32
+ # returning the type names of the available Marketo objects. The type
33
+ # names can be passed to #describe.
34
+ def list
35
+ extract_from_response(
36
+ call(:list_m_objects, nil),
37
+ :objects
38
+ )
39
+ end
40
+
41
+ # Implements
42
+ # {+describeMObject+}[http://developers.marketo.com/documentation/soap/describemobject/],
43
+ # returning the description of the Marketo object.
44
+ def describe(name)
45
+ unless MarketoAPI::MObject::DESCRIBE_TYPES.include?(name.to_sym)
46
+ raise ArgumentError, "invalid type #{name} to describe"
47
+ end
48
+
49
+ extract_from_response(
50
+ call(:describe_m_object, object_name: name),
51
+ :metadata
52
+ )
53
+ end
54
+
55
+ # Implements
56
+ # {+getMObjects+}[http://developers.marketo.com/documentation/soap/getmobjects/],
57
+ # returning one or more Marketo objects, up to 100 in a page. It also
58
+ # returns the current current stream position to continue working with the
59
+ # pages on subsequent calls to #get.
60
+ #
61
+ # See MObject#criteria and MObject#association on how to build criteria
62
+ # and association filters for #get queries.
63
+ def get(mobject)
64
+ call(:get_m_objects, transform_param(__method__, mobject)) { |list|
65
+ Get.new(list)
66
+ }
67
+ end
68
+
69
+ def sync(operation, *mobjects) #:nodoc:
70
+ # http://developers.marketo.com/documentation/soap/sync-mobjects/
71
+ raise NotImplementedError,
72
+ ":sync_m_objects is not implemented in this version."
73
+ response = call(
74
+ :sync_m_objects,
75
+ transform_param_list(__method__, mobjects)
76
+ )
77
+ extract_mobject_status_list(response)
78
+ end
79
+
80
+ # A response object to MObjects#get that includes the return count, the
81
+ # new stream position, and the list of MObject records.
82
+ class Get
83
+ # The number of MObjects returned from MObjects#get.
84
+ attr_reader :return_count
85
+ # The stream position used for paging in MObjects#get.
86
+ # This may be shared with each MObject in #mobjects.
87
+ attr_reader :stream_position
88
+ # The list of Marketo objects.
89
+ attr_reader :mobjects
90
+
91
+ def initialize(hash)
92
+ @return_count = hash[:return_count].to_i
93
+ @more = hash[:has_more]
94
+ @stream_position = hash[:new_stream_position]
95
+ objects = MarketoAPI.array(hash[:m_object_list])
96
+
97
+ @mobjects = objects.map { |object|
98
+ MarketoAPI::MObject.from_soap_hash(object[:m_object])
99
+ }
100
+ end
101
+
102
+ # Returns +true+ if there are more objects to be returned.
103
+ def more?
104
+ !!@more
105
+ end
106
+ end
107
+
108
+ private
109
+ def extract_mobject_status_list(response)
110
+ response = extract_from_response(response, :m_obj_status_list) { |resp|
111
+ resp.map { |e| e[:m_object_status].values_at(:id, :status) }
112
+ }.flatten
113
+ Hash[*response]
114
+ end
115
+ 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