diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5a7f523 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Description: +Code reviewed by: +Tested by: +PM reviewed by: +TeamCity passed: [yes] +Test plan: +Risk/complexity: [high|low] +Impact/scope: [high|low] diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee0d00f --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +marketo_gem +=========== +Fork of the original Rapleaf marketo gem to support updates to the Marketo API and fix some bugs. + +Use at your own risk. diff --git a/lib/marketo.rb b/lib/marketo.rb index 01906f0..64b0465 100644 --- a/lib/marketo.rb +++ b/lib/marketo.rb @@ -1,14 +1,12 @@ require 'rubygems' require 'savon' -Savon.configure do |config| - config.log = false # disable logging -end - require File.expand_path('marketo/client', File.dirname(__FILE__)) require File.expand_path('marketo/authentication_header', File.dirname(__FILE__)) require File.expand_path('marketo/enums', File.dirname(__FILE__)) +require File.expand_path('marketo/key', File.dirname(__FILE__)) require File.expand_path('marketo/lead_key', File.dirname(__FILE__)) +require File.expand_path('marketo/list_key', File.dirname(__FILE__)) require File.expand_path('marketo/lead_record', File.dirname(__FILE__)) diff --git a/lib/marketo/authentication_header.rb b/lib/marketo/authentication_header.rb index 9a81087..cc05922 100644 --- a/lib/marketo/authentication_header.rb +++ b/lib/marketo/authentication_header.rb @@ -1,7 +1,7 @@ -module Rapleaf +module Grabcad module Marketo # This class exists only to encapsulate the authentication header part of a soap request to marketo - # Marketo requires a somewhat complex calculation of an encrypted signature and so it seemed sensible to pull this code out here + # It contains a SHA1-based MAC based on a timestamp class AuthenticationHeader DIGEST = OpenSSL::Digest::Digest.new('sha1') @@ -12,36 +12,25 @@ def initialize(access_key, secret_key, time = DateTime.now) end public - # time should be a DateTime instance def set_time(time) @time = time end - def get_mktows_user_id - @access_key - end - - def get_request_signature - calculate_signature - end - - def get_request_timestamp - @time.to_s - end - def to_hash { - "mktowsUserId" => get_mktows_user_id, - "requestSignature" => get_request_signature, - "requestTimestamp" => get_request_timestamp + "mktowsUserId" => @access_key, + "requestSignature" => calculate_signature, + "requestTimestamp" => request_timestamp } end private + def request_timestamp + @time.to_s + end + def calculate_signature - request_timestamp = get_request_timestamp - string_to_encrypt = request_timestamp + @access_key - OpenSSL::HMAC.hexdigest(DIGEST, @secret_key, string_to_encrypt) + OpenSSL::HMAC.hexdigest(DIGEST, @secret_key, request_timestamp + @access_key) end end end diff --git a/lib/marketo/client.rb b/lib/marketo/client.rb index a973031..b634655 100644 --- a/lib/marketo/client.rb +++ b/lib/marketo/client.rb @@ -1,29 +1,37 @@ require File.expand_path('authentication_header', File.dirname(__FILE__)) -module Rapleaf +module Grabcad module Marketo - def self.new_client(access_key, secret_key, api_subdomain = 'na-i', api_version = '1_5', document_version = '1_4') - client = Savon::Client.new do - wsdl.endpoint = "https://#{api_subdomain}.marketo.com/soap/mktows/#{api_version}" - wsdl.document = "http://app.marketo.com/soap/mktows/#{document_version}?WSDL" - http.read_timeout = 90 - http.open_timeout = 90 - http.headers = {"Connection" => "Keep-Alive"} + def self.create_client (access_key, secret_key, endpoint_uri, wsdl_uri = nil) + savon_client = Savon.client do + endpoint endpoint_uri + if wsdl_uri + wsdl wsdl_uri + else + #default to Marketo API version 2.1 with cached WSDL + wsdl File.expand_path('../wsdl/marketo_2_1.wsdl', File.dirname(__FILE__)) + end + read_timeout 90 + open_timeout 90 + headers ({ "Connection" => "Keep-Alive" }) + log_level :debug + log false + pretty_print_xml true end - - Client.new(client, Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key)) + Client.new(savon_client, Grabcad::Marketo::AuthenticationHeader.new(access_key, secret_key)) end - # = The client for talking to marketo - # based on the SOAP wsdl file: http://app.marketo.com/soap/mktows/1_4?WSDL + # Based on the WSDL for Marketo API V2.1, cached locally in ../wsdl/marketo_2_1.wsdl. + # To select another version of the API, specify the URI to the WSDL as the optional 4th argument to create_client. + # You will need to verify that the gem still functions properly against the new version of the API. # # Usage: # - # client = Rapleaf::Marketo.new_client(, , api_subdomain = 'na-i', api_version = '1_5', document_version = '1_4') + # client = Grabcad::Marketo.create_client(, , , []) # - # == get_lead_by_email: + # == get a lead by email: # - # lead_record = client.get_lead_by_email('sombody@examnple.com') + # lead_record = client.get_lead_by_email('example@email.com') # # puts lead_record.idnum # @@ -31,41 +39,44 @@ def self.new_client(access_key, secret_key, api_subdomain = 'na-i', api_version # # puts lead_record.get_attribute('LastName') # - # == sync_lead: (update) - # - # lead_record = client.sync_lead('example@rapleaf.com', 'Joe', 'Smith', 'Company 1', '415 911') - # - # == sync_lead_record: (update with custom fields) + # == insert/update a new lead: (upsert_lead) # - # lead_record = Rapleaf::Marketo::LeadRecord.new('harry@rapleaf.com') + # lead_record = client.upsert_lead('new_example@email.com', 'Test', 'Lead', 'Initech') # - # lead_record.set_attribute('FirstName', 'harry') + # == update fields: (update with custom fields) # - # lead_record.set_attribute('LastName', 'smith') + # lead_record = client.get_lead_by_email('example@email.com') # - # lead_record.set_attribute('Email', 'harry@somesite.com') + # lead_record.set_attribute('FirstName', 'NewFirst') # - # lead_record.set_attribute('Company', 'Rapleaf') + # lead_record.set_attribute('LastName', 'NewLast') # - # lead_record.set_attribute('MobilePhone', '123 456') + # lead_record.set_attribute('Email', 'updated@email.com') # - # response = client.sync_lead_record(lead_record) + # lead_record.set_attribute('Company', 'Initech2') # - # == sync_lead_record_on_id: (update with custom fields, ensuring the sync is id based) + # lead_record.set_attribute(, ) # - # similarly, you can force a sync via id instead of email by calling client.sync_lead_record_on_id(lead_record) + # lead.sync # class Client - # This constructor is used internally, create your client with *Rapleaf::Marketo.new_client(, )* + # This constructor is used internally, create your client with *Grabcad::Marketo.create_client(, , )* def initialize(savon_client, authentication_header) @client = savon_client @header = authentication_header + + if ENV["RAILS_ENV"] && Rails && Rails.logger + logger=Rails.logger + else + logger=Logger.new(STDOUT) + end + @client.globals.log true end public - def get_lead_by_idnum(idnum) - get_lead(LeadKey.new(LeadKeyType::IDNUM, idnum)) + def get_lead_by_lead_id(lead_id) + get_lead(LeadKey.new(LeadKeyType::IDNUM, lead_id)) end @@ -73,8 +84,32 @@ def get_lead_by_email(email) get_lead(LeadKey.new(LeadKeyType::EMAIL, email)) end - def set_logger(logger) + def logger=(logger) #Specify a logger compatible with ruby logger @logger = logger + @client.globals.logger logger + HTTPI.logger = logger + end + + def log_level=(level) + if (level.kind_of? (Fixnum)) + @logger.level = level if @logger + symbolic_level = case level + when Logger::DEBUG + :debug + when Logger::INFO + :info + when Logger::WARN + :warn + when Logger::ERROR + :error + else + :fatal + end + @client.globals.log_level symbolic_level + HTTPI.log_level = symbolic_level + else + false + end end # create (if new) or update (if existing) a lead @@ -83,42 +118,42 @@ def set_logger(logger) # * first - first name of lead # * last - surname/last name of lead # * company - company the lead is associated with - # * mobile - mobile/cell phone number # # returns the LeadRecord instance on success otherwise nil - def sync_lead(email, first, last, company, mobile) - lead_record = LeadRecord.new(email) + def upsert_lead(email, first, last, company, lead_id = nil) + lead_record = LeadRecord.new(self, email, lead_id) lead_record.set_attribute('FirstName', first) lead_record.set_attribute('LastName', last) lead_record.set_attribute('Email', email) lead_record.set_attribute('Company', company) - lead_record.set_attribute('MobilePhone', mobile) - sync_lead_record(lead_record) + lead_record.sync end - def sync_lead_record(lead_record) + def sync_lead_record_by_email(lead_record) + raise 'Email not set - Cannot sync lead record without email' if lead_record.email.nil? + begin attributes = [] lead_record.each_attribute_pair do |name, value| attributes << {:attr_name => name, :attr_type => 'string', :attr_value => value} end - response = send_request("ns1:paramsSyncLead", { - :return_lead => true, - :lead_record => - {:email => lead_record.email, - :lead_attribute_list => { - :attribute => attributes}}}) - return LeadRecord.from_hash(response[:success_sync_lead][:result][:lead_record]) + response = send_request(:sync_lead, { + :return_lead => true, + :lead_record => { + :email => lead_record.email, + :lead_attribute_list => { :attribute => attributes } + } + }) + response[:success_sync_lead][:result][:lead_record] rescue Exception => e - @logger.log(e) if @logger - return nil + log_exception e + nil end end - def sync_lead_record_on_id(lead_record) - idnum = lead_record.idnum - raise 'lead record id not set' if idnum.nil? + def sync_lead_record_by_id(lead_record) + raise 'ID not set - Cannot sync lead record without ID' if lead_record.id.nil? begin attributes = [] @@ -126,76 +161,95 @@ def sync_lead_record_on_id(lead_record) attributes << {:attr_name => name, :attr_type => 'string', :attr_value => value} end - attributes << {:attr_name => 'Id', :attr_type => 'string', :attr_value => idnum.to_s} + attributes << {:attr_name => 'Id', :attr_type => 'string', :attr_value => lead_record.id.to_s} - response = send_request("ns1:paramsSyncLead", { - :return_lead => true, - :lead_record => - { - :lead_attribute_list => { :attribute => attributes}, - :id => idnum - }}) - return LeadRecord.from_hash(response[:success_sync_lead][:result][:lead_record]) + response = send_request(:sync_lead, { + :return_lead => true, + :lead_record => { + :lead_attribute_list => { :attribute => attributes }, + :id => lead_record.id + } + }) + response[:success_sync_lead][:result][:lead_record] rescue Exception => e - @logger.log(e) if @logger - return nil + log_exception e + nil end end - def add_to_list(list_key, email) - list_operation(list_key, ListOperationType::ADD_TO, email) + #Returns true if the operation succeeded, false otherwise + def add_to_list(list_id, lead) + list_operation(list_id_key(list_id), ListOperationType::ADD_TO, lead.id)[:success_list_operation][:result][:success] end - def remove_from_list(list_key, email) - list_operation(list_key, ListOperationType::REMOVE_FROM, email) + #Returns true if the operation succeeded, false otherwise + def remove_from_list(list_id, lead) + list_operation(list_id_key(list_id), ListOperationType::REMOVE_FROM, lead.id)[:success_list_operation][:result][:success] end - def is_member_of_list?(list_key, email) - list_operation(list_key, ListOperationType::IS_MEMBER_OF, email) + #Returns true if the operation succeeded, false otherwise + def is_member_of_list?(list_id, lead) + list_operation(list_id_key(list_id), ListOperationType::IS_MEMBER_OF, lead.id)[:success_list_operation][:result][:status_list][:lead_status][:status] end - private - def list_operation(list_key, list_operation_type, email) - begin - response = send_request("ns1:paramsListOperation", { + def enable_soap_debugging + @client.pretty_print_xml = true + end + + def log_exception(exp) + if @logger + @logger.warn(exp) + @logger.warn(exp.backtrace) + end + end + + # private + def list_operation(list_key, list_operation_type, id) + begin + response = send_request(:list_operation, { :list_operation => list_operation_type, - :list_key => list_key, + :list_key => list_key.to_hash, :strict => 'false', :list_member_list => { :lead_key => [ - {:key_type => 'EMAIL', :key_value => email} + {:key_type => LeadKeyType::IDNUM, :key_value => id} ] } }) return response rescue Exception => e - @logger.log(e) if @logger + log_exception e return nil end end + def list_id_key(list_id) + ListKey.new(ListKeyType::MKTOLISTNAME, list_id) + end + + private + def get_lead(lead_key) begin - response = send_request("ns1:paramsGetLead", {:lead_key => lead_key.to_hash}) - return LeadRecord.from_hash(response[:success_get_lead][:result][:lead_record_list][:lead_record]) + response = send_request(:get_lead, {:lead_key => lead_key.to_hash}) + LeadRecord.from_hash(self, response[:success_get_lead][:result][:lead_record_list][:lead_record]) rescue Exception => e - @logger.log(e) if @logger - return nil + log_exception e + nil end end - def send_request(namespace, body) + def send_request(operation, body) @header.set_time(DateTime.now) - response = request(namespace, body, @header.to_hash) - response.to_hash + response = request(operation, body, @header.to_hash) + response.body end - def request(namespace, body, header) - @client.request namespace do |soap| - soap.namespaces["xmlns:ns1"] = "http://www.marketo.com/mktows/" - soap.body = body - soap.header["ns1:AuthenticationHeader"] = header - end + def request(operation, body, header) #returns a Savon 2 response + auth_header = {"tns:AuthenticationHeader" => header.to_hash} + # set the marketo-specific authentication header with authentication SHA1 + @client.globals.soap_header(auth_header) + @client.call(operation, message: body) end end end diff --git a/lib/marketo/enums.rb b/lib/marketo/enums.rb index 6ee3d25..4f37763 100644 --- a/lib/marketo/enums.rb +++ b/lib/marketo/enums.rb @@ -1,4 +1,4 @@ -module Rapleaf +module Grabcad module Marketo # Types of operations you can do on a marketo list module ListOperationType @@ -19,5 +19,12 @@ module LeadKeyType SFDCLEADOWNERID = "SFDCLEADOWNERID" SFDCOPPTYID = "SFDCOPPTYID" end + + # Types of keys that can be used to look up a list + module ListKeyType + MKTOLISTNAME ="MKTOLISTNAME" + MKTOSALESUSERID ="MKTOSALESUSERID" + SFDCLEADOWNERID ="SFDCLEADOWNERID" + end end end \ No newline at end of file diff --git a/lib/marketo/key.rb b/lib/marketo/key.rb new file mode 100644 index 0000000..7d25862 --- /dev/null +++ b/lib/marketo/key.rb @@ -0,0 +1,31 @@ +module Grabcad + module Marketo + # Encapsulates a Marketo type-value key + class Key + # - *key_type* the type of key to use see LeadKeyType + # - *key_value* normally a string value for the given type + def initialize(key_type, key_value) + @key_type = key_type + @key_value = key_value + end + + # get the key type + def key_type + @key_type + end + + # get the key value + def key_value + @key_value + end + + # create a hash from this instance, for sending this object to marketo using savon + def to_hash + { + :key_type => @key_type, + :key_value => @key_value + } + end + end + end +end \ No newline at end of file diff --git a/lib/marketo/lead_key.rb b/lib/marketo/lead_key.rb index 09c1da1..d9ebeee 100644 --- a/lib/marketo/lead_key.rb +++ b/lib/marketo/lead_key.rb @@ -1,31 +1,6 @@ -module Rapleaf +module Grabcad module Marketo - # Encapsulates a key used to look up or describe a specific marketo lead. - class LeadKey - # - *key_type* the type of key to use see LeadKeyType - # - *key_value* normally a string value for the given type - def initialize(key_type, key_value) - @key_type = key_type - @key_value = key_value - end - - # get the key type - def key_type - @key_type - end - - # get the key value - def key_value - @key_value - end - - # create a hash from this instance, for sending this object to marketo using savon - def to_hash - { - :key_type => @key_type, - :key_value => @key_value - } - end + class LeadKey < Key end end -end \ No newline at end of file +end diff --git a/lib/marketo/lead_record.rb b/lib/marketo/lead_record.rb index abafa59..808e702 100644 --- a/lib/marketo/lead_record.rb +++ b/lib/marketo/lead_record.rb @@ -1,25 +1,34 @@ -module Rapleaf +module Grabcad module Marketo # Represents a record of the data known about a lead within marketo class LeadRecord - def initialize(email, idnum = nil) - @idnum = idnum + attr_reader :id, :attributes + + def initialize(client, email, lead_id = nil, attrs = nil) + @client = client + @id = lead_id @attributes = {} + if (attrs) + populate_from attrs + end set_attribute('Email', email) end - # hydrates an instance from a savon hash returned form the marketo API - def self.from_hash(savon_hash) - lead_record = LeadRecord.new(savon_hash[:email], savon_hash[:id].to_i) - savon_hash[:lead_attribute_list][:attribute].each do |attribute| - lead_record.set_attribute(attribute[:attr_name], attribute[:attr_value]) + # creates and populates an instance from a savon hash returned form the marketo API + def self.from_hash(client, lead_hash) + # if there's more than one lead, take the first one. + if lead_hash.kind_of? Array + lead_hash = lead_hash[0] end - lead_record + lead_record = LeadRecord.new(client, lead_hash[:email], lead_hash[:id].to_i, lead_hash) end - # get the record idnum - def idnum - @idnum + def sync + if @id + populate_from @client.sync_lead_record_by_id(self) + else + populate_from @client.sync_lead_record_by_email(self) + end end # get the record email @@ -27,10 +36,6 @@ def email get_attribute('Email') end - def attributes - @attributes - end - # update the value of the named attribute def set_attribute(name, value) @attributes[name] = value @@ -50,7 +55,43 @@ def each_attribute_pair(&block) def ==(other) @attributes == other.attributes && - @idnum == other.idnum + @id == other.id + end + + # List operations + def add_to_list(list_id) + @client.add_to_list(list_id, self) + end + + def remove_from_list(list_id) + @client.remove_from_list(list_id, self) + end + + def is_member_of_list?(list_id) + @client.is_member_of_list?(list_id, self) + end + + private + + def populate_from(lead_hash) + begin + @id = lead_hash[:id].to_i + lead_attributes = lead_hash[:lead_attribute_list] + if lead_attributes # if no attributes set + attrs = lead_attributes[:attribute] + if attrs.kind_of?(Array) # if only one attribute set, service doesn't return an array + attrs.each do |attribute| + set_attribute(attribute[:attr_name], attribute[:attr_value]) + end + else + set_attribute(attrs[:attr_name], attrs[:attr_value]) + end + end + self + rescue Exception => e + @client.log_exception e + nil + end end end end diff --git a/lib/marketo/list_key.rb b/lib/marketo/list_key.rb new file mode 100644 index 0000000..35e364f --- /dev/null +++ b/lib/marketo/list_key.rb @@ -0,0 +1,6 @@ +module Grabcad + module Marketo + class ListKey < Key + end + end +end diff --git a/lib/wsdl/marketo_2_1.wsdl b/lib/wsdl/marketo_2_1.wsdl new file mode 100644 index 0000000..820772e --- /dev/null +++ b/lib/wsdl/marketo_2_1.wsdl @@ -0,0 +1,1630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete one or more MObject. + + + + + + Get meta data for a MObject. + + + + + + Get one or more MObject. + + + + + + Create, update, or upsert MObject. + + + + + + Get a list campaigns. + + + + + + Provides information regarding the status of list import. + + + + + + Get all details about a lead. + + + + + + Get all activity log details about a lead. + + + + + + Get changes for all leads. + + + + + + Get all details about one or more leads. + + + + + + Imports the list from web file info + + + + + + Send a Marketo email. + + + + + + Perform an operation on a Marketo List, like add lead or remove lead. + + + + + + Request to add a set of leads to a campaign. + + + + + + Request to add tokens to a schedule campaign. + + + + + + Create or update a lead. + + + + + + Create or update one or more leads. + + + + + + Update, Insert, or Upsert custom object records + + + + + + Delete custom object records + + + + + + Get custom object records + + + + + + Merge Leads + + + + + + Get Tags + + + + + + Get Tags + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marketo.gemspec b/marketo.gemspec index ebf5fdc..17f2a4d 100644 --- a/marketo.gemspec +++ b/marketo.gemspec @@ -3,21 +3,18 @@ Gem::Specification.new do |gem| gem.summary = "A client for the marketo API" gem.description = <<-EOF Allows easy integration with marketo from ruby. You can synchronize leads and fetch them back by email. - By default this is configured for the SOAP wsdl file: http://app.marketo.com/soap/mktows/1_4?WSDL but this is - configurable when you construct the client, e.g. - client = Rapleaf::Marketo.new_client(, , (api_subdomain = 'na-i'), (api_version = '1_5'), (document_version = '1_4')) - More information at https://www.rapleaf.com/developers/marketo. + Forked from the Rapleaf Marketo API Gem, with updates for Marketo API 2.x and Savon 2.x. EOF - gem.email = "james@rapleaf.com" - gem.authors = ["James O'Brien"] - gem.homepage = "https://www.rapleaf.com/developers/marketo" + gem.email = "joey@grabcad.com" + gem.authors = ["Joseph George"] + gem.homepage = "https://github.com/jgeorge-gc/marketo_gem" gem.files = Dir['lib/**/*.rb'] gem.require_path = ['lib'] gem.test_files = Dir['spec/**/*_spec.rb'] - gem.version = "1.4.0" + gem.version = "0.0.10" gem.has_rdoc = true - gem.rdoc_options << '--title' << 'Marketo Client Gem' << '--main' << 'Rapleaf::Marketo::Client' + gem.rdoc_options << '--title' << 'Marketo Client Gem, updated' << '--main' << 'Grabcad::Marketo::Client' gem.add_development_dependency('rspec', '>= 2.3.0') - gem.add_dependency('savon', '>= 0.8.3') + gem.add_dependency('savon', '~> 2.2') end diff --git a/spec/fixtures/add_lead_to_list.xml b/spec/fixtures/add_lead_to_list.xml new file mode 100644 index 0000000..e536d7f --- /dev/null +++ b/spec/fixtures/add_lead_to_list.xml @@ -0,0 +1,11 @@ + + + + + + true + + + + + \ No newline at end of file diff --git a/spec/fixtures/get_lead_response.xml b/spec/fixtures/get_lead_response.xml new file mode 100644 index 0000000..df444c5 --- /dev/null +++ b/spec/fixtures/get_lead_response.xml @@ -0,0 +1,40 @@ + + + + + + 1 + + + 12345 + some@email.com + + + + + City + string + Utopia + + + FirstName + string + Test + + + LastName + string + Lead + + + MiddleName + string + This + + + + + + + + \ No newline at end of file diff --git a/spec/fixtures/insert_lead_response.xml b/spec/fixtures/insert_lead_response.xml new file mode 100644 index 0000000..173da88 --- /dev/null +++ b/spec/fixtures/insert_lead_response.xml @@ -0,0 +1,43 @@ + + + + + + 12345 + + 12345 + CREATED + + + + 12345 + some@email.com + + + + + Company + string + Initech + + + FirstName + string + Test + + + LastName + string + Lead + + + Website + url + initech.com + + + + + + + \ No newline at end of file diff --git a/spec/fixtures/is_lead_in_list_false.xml b/spec/fixtures/is_lead_in_list_false.xml new file mode 100644 index 0000000..c9bf537 --- /dev/null +++ b/spec/fixtures/is_lead_in_list_false.xml @@ -0,0 +1,19 @@ + + + + + + true + + + + IDNUM + 12345 + + false + + + + + + \ No newline at end of file diff --git a/spec/fixtures/is_lead_in_list_true.xml b/spec/fixtures/is_lead_in_list_true.xml new file mode 100644 index 0000000..9e18307 --- /dev/null +++ b/spec/fixtures/is_lead_in_list_true.xml @@ -0,0 +1,19 @@ + + + + + + true + + + + IDNUM + 12345 + + true + + + + + + \ No newline at end of file diff --git a/spec/fixtures/remove_lead_from_list.xml b/spec/fixtures/remove_lead_from_list.xml new file mode 100644 index 0000000..e536d7f --- /dev/null +++ b/spec/fixtures/remove_lead_from_list.xml @@ -0,0 +1,11 @@ + + + + + + true + + + + + \ No newline at end of file diff --git a/spec/fixtures/sync_lead_response.xml b/spec/fixtures/sync_lead_response.xml new file mode 100644 index 0000000..59ec5dc --- /dev/null +++ b/spec/fixtures/sync_lead_response.xml @@ -0,0 +1,43 @@ + + + + + + 12345 + + 12345 + UPDATED + + + + 12345 + some@email.com + + + + + City + string + Utopia + + + FirstName + string + Test + + + LastName + string + Lead + + + MiddleName + string + Updated + + + + + + + \ No newline at end of file diff --git a/spec/marketo/authentication_header_spec.rb b/spec/marketo/authentication_header_spec.rb index 6f0609f..132f6cc 100644 --- a/spec/marketo/authentication_header_spec.rb +++ b/spec/marketo/authentication_header_spec.rb @@ -1,66 +1,115 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) -module Rapleaf +module Grabcad module Marketo - ACCESS_KEY = 'ACCESS_KEY' - SECRET_KEY = 'SECRET_KEY' + #Test values from Marketo API docs + TEST_ACCESS_KEY = 'bigcorp1_461839624B16E06BA2D663' + TEST_SECRET_KEY = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66' + TEST_DATE = DateTime.new(2010, 4, 9, 14, 4, 54, -7/24.0) + TEST_DATE_STRING = '2010-04-09T14:04:54-07:00' + TEST_SIGNATURE = 'ffbff4d4bef354807481e66dc7540f7890523a87' + + + # hash keys with names from Marketo API + SIGNATURE_KEY = 'requestSignature' + USER_KEY = 'mktowsUserId' + TIMESTAMP_KEY = 'requestTimestamp' + + # expected structure of hash output + TEST_HASH = { + USER_KEY => TEST_ACCESS_KEY, + SIGNATURE_KEY => TEST_SIGNATURE, + TIMESTAMP_KEY => TEST_DATE_STRING, + } describe AuthenticationHeader do - it "should set mktowsUserId to access key" do - header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY) - header.get_mktows_user_id.should == ACCESS_KEY - end + describe "tests with 3-argument constructor" do + before(:each) do + @auth_header = Grabcad::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY, TEST_DATE) + @auth_hash = @auth_header.to_hash + end - it "should set requestSignature" do - header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY) - header.get_request_signature.should_not be_nil - header.get_request_signature.should_not == '' + it "to_hash should create the proper hash for known inputs" do + @auth_hash.should == TEST_HASH + end + it "hash should change if time changes" do + @auth_header.set_time(DateTime.now) + hash = @auth_header.to_hash + @auth_hash[USER_KEY].should == hash[USER_KEY] + @auth_hash[SIGNATURE_KEY].should_not == hash[SIGNATURE_KEY] + @auth_hash[TIMESTAMP_KEY].should_not == hash[TIMESTAMP_KEY] + end end - - it "should set requestTimestamp in correct format" do - header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY) - time = DateTime.new(1998, 1, 17, 20, 15, 1) - header.set_time(time) - header.get_request_timestamp().should == '1998-01-17T20:15:01+00:00' + + describe "tests with 2-argument constructor" do + before(:each) do + @auth_header = Grabcad::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) + @auth_hash = @auth_header.to_hash + end + it "should format the time correctly" do + @auth_header.set_time(TEST_DATE) + @auth_header.to_hash[TIMESTAMP_KEY].should == TEST_DATE_STRING + end + it "should create a hash with the default time" do + @auth_hash[USER_KEY].should be_a(String) + @auth_hash[SIGNATURE_KEY].should be_a(String) + @auth_hash[TIMESTAMP_KEY].should be_a(String) + end end + end + end +end + # it "should set mktowsUserId to access key" do + # header = Rapleaf::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) + # header.get_mktows_user_id.should == TEST_ACCESS_KEY + # end - it "should calculate encrypted signature" do - # I got this example of the marketo API docs + # it "should set requestSignature" do + # header = Rapleaf::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) - access_key = 'bigcorp1_461839624B16E06BA2D663' - secret_key = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66' + # header.get_request_signature.should_not be_nil + # header.get_request_signature.should_not == '' + # end - header = Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key) - header.set_time(DateTime.new(2010, 4, 9, 14, 4, 55, -7/24.0)) + # it "should set requestTimestamp in correct format" do + # header = Rapleaf::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) + # header.set_time(TEST_DATE) - header.get_request_timestamp.should == '2010-04-09T14:04:54-07:00' - header.get_request_signature.should == 'ffbff4d4bef354807481e66dc7540f7890523a87' - end + # header.get_request_timestamp().should == TEST_DATE_STRING + # end - it "should cope if no date is given" do - header = Rapleaf::Marketo::AuthenticationHeader.new(ACCESS_KEY, SECRET_KEY) - expected = DateTime.now - actual = DateTime.parse(header.get_request_timestamp) + # it "should calculate encrypted signature" do + # header = Rapleaf::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) + # header.set_time(TEST_DATE) - expected.year.should == actual.year - expected.hour.should == actual.hour - end + # header.get_request_timestamp.should == TEST_DATE_STRING + # header.get_request_signature.should == TEST_SIGNATURE + # end - it "should to_hash correctly" do - # I got this example from the marketo API docs + # it "should cope if no date is given" do + # header = Rapleaf::Marketo::AuthenticationHeader.new(TEST_ACCESS_KEY, TEST_SECRET_KEY) + # expected = DateTime.now + # actual = DateTime.parse(header.get_request_timestamp) - access_key = 'bigcorp1_461839624B16E06BA2D663' - secret_key = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66' + # expected.year.should == actual.year + # expected.hour.should == actual.hour + # end - header = Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key) - header.set_time(DateTime.new(2010, 4, 9, 14, 4, 55, -7/24.0)) + # it "should to_hash correctly" do + # # taken from marketo API docs - header.to_hash.should == { - 'mktowsUserId' => header.get_mktows_user_id, - 'requestSignature' => header.get_request_signature, - 'requestTimestamp' => header.get_request_timestamp, - } - end - end - end -end \ No newline at end of file + # access_key = 'bigcorp1_461839624B16E06BA2D663' + # secret_key = '899756834129871744AAEE88DDCC77CDEEDEC1AAAD66' + + # header = Rapleaf::Marketo::AuthenticationHeader.new(access_key, secret_key) + # header.set_time(DateTime.new(2010, 4, 9, 14, 4, 55, -7/24.0)) + + # header.to_hash.should == { + # 'mktowsUserId' => header.get_mktows_user_id, + # 'requestSignature' => header.get_request_signature, + # 'requestTimestamp' => header.get_request_timestamp, + # } + # end + # end + # end +#end \ No newline at end of file diff --git a/spec/marketo/client_spec.rb b/spec/marketo/client_spec.rb index ed9e0cd..7c3e62f 100644 --- a/spec/marketo/client_spec.rb +++ b/spec/marketo/client_spec.rb @@ -1,363 +1,143 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) +require 'savon/mock/spec_helper' -module Rapleaf +module Grabcad module Marketo - describe Client do - EMAIL = "some@email.com" - IDNUM = 29 - FIRST = 'Joe' - LAST = 'Smith' - COMPANY = 'Rapleaf' - MOBILE = '415 123 456' - API_KEY = 'API123KEY' + include Savon::SpecHelper + # since the test does not specify a local WSDL, it will exhibit the behavior defined here https://github.com/savonrb/savon/issues/396 + + EMAIL = "some@email.com" + IDNUM = 12345 + FIRST = 'Test' + LAST = 'Lead' + COMPANY = 'Initech' + MOBILE = '415 123 456' + + ACCESS_KEY = 'ACCESS_KEY' + SECRET_KEY = 'SECRET_KEY' + ENDPOINT = "mock_endpoint" + + TEST_LIST = "TestList" + + before(:all) { savon.mock! } + after(:all) { savon.unmock! } + + it "should return a marketo client" do + client = Grabcad::Marketo.create_client(ACCESS_KEY, SECRET_KEY, ENDPOINT) + client.should_not be_nil; + end - context 'Exception handling' do - it "should return nil if any exception is raised on get_lead request" do - savon_client = mock('savon_client').as_null_object - authentication_header = mock('authentication_header').as_null_object - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - savon_client.should_receive(:request).and_raise Exception - client.get_lead_by_email(EMAIL).should be_nil - end + it "should get a lead by email" do + fixture = File.read("spec/fixtures/get_lead_response.xml") + savon.expects(:get_lead).with(message: {:lead_key=>{:key_type=>"EMAIL", :key_value=>EMAIL}} ).returns(fixture) - it "should return nil if any exception is raised on sync_lead request" do - savon_client = mock('savon_client').as_null_object - authentication_header = mock('authentication_header').as_null_object - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - savon_client.should_receive(:request).and_raise Exception - client.sync_lead(EMAIL, FIRST, LAST, COMPANY, MOBILE).should be_nil - end + marketo = Grabcad::Marketo.create_client(ACCESS_KEY, SECRET_KEY, ENDPOINT) + marketo.logger=(Logger.new(STDOUT)) + marketo.log_level = Logger::DEBUG + lead = marketo.get_lead_by_email(EMAIL) + lead.should_not be_nil + lead.email.should == EMAIL end - context 'Client interaction' do - it "should have the correct body format on get_lead_by_idnum" do - savon_client = mock('savon_client') - authentication_header = mock('authentication_header') - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - response_hash = { - :success_get_lead => { - :result => { - :count => 1, - :lead_record_list => { - :lead_record => { - :email => EMAIL, - :lead_attribute_list => { - :attribute => [ - {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'}, - {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'}, - {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'}, - {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'} - ] - }, - :foreign_sys_type => nil, - :foreign_sys_person_id => nil, - :id => IDNUM.to_s - } - } - } - } - } - expect_request(savon_client, - authentication_header, - equals_matcher(:lead_key => { - :key_value => IDNUM, - :key_type => LeadKeyType::IDNUM - }), - 'ns1:paramsGetLead', - response_hash) - expected_lead_record = LeadRecord.new(EMAIL, IDNUM) - expected_lead_record.set_attribute('name1', 'val1') - expected_lead_record.set_attribute('name2', 'val2') - expected_lead_record.set_attribute('name3', 'val3') - expected_lead_record.set_attribute('name4', 'val4') - client.get_lead_by_idnum(IDNUM).should == expected_lead_record - end - - it "should have the correct body format on get_lead_by_email" do - savon_client = mock('savon_client') - authentication_header = mock('authentication_header') - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - response_hash = { - :success_get_lead => { - :result => { - :count => 1, - :lead_record_list => { - :lead_record => { - :email => EMAIL, - :lead_attribute_list => { - :attribute => [ - {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'}, - {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'}, - {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'}, - {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'} - ] - }, - :foreign_sys_type => nil, - :foreign_sys_person_id => nil, - :id => IDNUM.to_s - } - } - } - } - } - expect_request(savon_client, - authentication_header, - equals_matcher({:lead_key => { - :key_value => EMAIL, - :key_type => LeadKeyType::EMAIL}}), - 'ns1:paramsGetLead', - response_hash) - expected_lead_record = LeadRecord.new(EMAIL, IDNUM) - expected_lead_record.set_attribute('name1', 'val1') - expected_lead_record.set_attribute('name2', 'val2') - expected_lead_record.set_attribute('name3', 'val3') - expected_lead_record.set_attribute('name4', 'val4') - client.get_lead_by_email(EMAIL).should == expected_lead_record - end + it "should get a lead by id" do + fixture = File.read("spec/fixtures/get_lead_response.xml") + savon.expects(:get_lead).with(message: {:lead_key=>{:key_type=>"IDNUM", :key_value=>IDNUM}} ).returns(fixture) - it "should have the correct body format on sync_lead_record" do - savon_client = mock('savon_client') - authentication_header = mock('authentication_header') - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - response_hash = { - :success_sync_lead => { - :result => { - :lead_id => IDNUM, - :sync_status => { - :error => nil, - :status => 'UPDATED', - :lead_id => IDNUM - }, - :lead_record => { - :email => EMAIL, - :lead_attribute_list => { - :attribute => [ - {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'}, - {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'}, - {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'}, - {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'} - ] - }, - :foreign_sys_type => nil, - :foreign_sys_person_id => nil, - :id => IDNUM.to_s - } - } - } - } - expect_request(savon_client, - authentication_header, - (Proc.new do |actual| - retval = true - retval = false unless actual[:return_lead] - retval = false unless actual[:lead_record][:email].equal?(EMAIL) - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].size == 5 - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => EMAIL, :attr_name => "Email", :attr_type => "string"}) - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val1", :attr_name => "name1", :attr_type => "string"}) - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val2", :attr_name => "name2", :attr_type => "string"}) - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val3", :attr_name => "name3", :attr_type => "string"}) - retval = false unless actual[:lead_record][:lead_attribute_list][:attribute].include?({:attr_value => "val4", :attr_name => "name4", :attr_type => "string"}) - retval.should == true - end), - 'ns1:paramsSyncLead', - response_hash) - lead_record = LeadRecord.new(EMAIL, IDNUM) - lead_record.set_attribute('name1', 'val1') - lead_record.set_attribute('name2', 'val2') - lead_record.set_attribute('name3', 'val3') - lead_record.set_attribute('name4', 'val4') + marketo = Grabcad::Marketo.create_client(ACCESS_KEY, SECRET_KEY, ENDPOINT) + marketo.logger=(Logger.new(STDOUT)) + marketo.log_level = Logger::DEBUG + lead = marketo.get_lead_by_lead_id(IDNUM) + lead.should_not be_nil + lead.id.should == IDNUM + end - client.sync_lead_record(lead_record).should == lead_record - end + it "should create a new lead" do + fixture = File.read("spec/fixtures/insert_lead_response.xml") + savon.expects(:sync_lead).with(message: {:return_lead=>true, + :lead_record=> + {:email=>"some@email.com", + :lead_attribute_list=> + {:attribute=> + [{:attr_name=>"Email", + :attr_type=>"string", + :attr_value=>EMAIL}, + {:attr_name=>"FirstName", :attr_type=>"string", :attr_value=>FIRST}, + {:attr_name=>"LastName", :attr_type=>"string", :attr_value=>LAST}, + {:attr_name=>"Company", + :attr_type=>"string", + :attr_value=>COMPANY}]}}} ).returns(fixture) + marketo = Grabcad::Marketo.create_client(ACCESS_KEY, SECRET_KEY, ENDPOINT) + marketo.logger=(Logger.new(STDOUT)) + marketo.log_level = Logger::DEBUG + lead = marketo.upsert_lead(EMAIL, FIRST, LAST, COMPANY) + lead.get_attribute("FirstName").should == FIRST + lead.email.should == EMAIL + lead.get_attribute("LastName").should == LAST + lead.get_attribute("Company").should == COMPANY + lead.id.should == IDNUM + end - it "should have the correct body format on sync_lead" do - savon_client = mock('savon_client') - authentication_header = mock('authentication_header') - client = Rapleaf::Marketo::Client.new(savon_client, authentication_header) - response_hash = { - :success_sync_lead => { - :result => { - :lead_id => IDNUM, - :sync_status => { - :error => nil, - :status => 'UPDATED', - :lead_id => IDNUM - }, - :lead_record => { - :email => EMAIL, - :lead_attribute_list => { - :attribute => [ - {:attr_name => 'name1', :attr_type => 'string', :attr_value => 'val1'}, - {:attr_name => 'name2', :attr_type => 'string', :attr_value => 'val2'}, - {:attr_name => 'name3', :attr_type => 'string', :attr_value => 'val3'}, - {:attr_name => 'name4', :attr_type => 'string', :attr_value => 'val4'} - ] - }, - :foreign_sys_type => nil, - :foreign_sys_person_id => nil, - :id => IDNUM.to_s - } - } - } - } + describe "given a lead" do + before(:all) do + fixture = File.read("spec/fixtures/get_lead_response.xml") + savon.expects(:get_lead).with(message: {:lead_key=>{:key_type=>"IDNUM", :key_value=>IDNUM}} ).returns(fixture) - expect_request(savon_client, - authentication_header, - Proc.new { |actual| - actual_attribute_list = actual[:lead_record][:lead_attribute_list][:attribute] - actual[:lead_record][:lead_attribute_list][:attribute] = nil - expected = { - :return_lead => true, - :lead_record => { - :email => "some@email.com", - :lead_attribute_list => - { - :attribute => nil}} - } - actual.should == expected - actual_attribute_list.should =~ [ - {:attr_value => FIRST, - :attr_name => "FirstName", - :attr_type => "string"}, - {:attr_value => LAST, - :attr_name => "LastName", - :attr_type => "string"}, - {:attr_value => EMAIL, - :attr_name =>"Email", - :attr_type => "string"}, - {:attr_value => COMPANY, - :attr_name => "Company", - :attr_type => "string"}, - {:attr_value => MOBILE, - :attr_name => "MobilePhone", - :attr_type => "string"} - ] - }, - 'ns1:paramsSyncLead', - response_hash) - expected_lead_record = LeadRecord.new(EMAIL, IDNUM) - expected_lead_record.set_attribute('name1', 'val1') - expected_lead_record.set_attribute('name2', 'val2') - expected_lead_record.set_attribute('name3', 'val3') - expected_lead_record.set_attribute('name4', 'val4') - client.sync_lead(EMAIL, FIRST, LAST, COMPANY, MOBILE).should == expected_lead_record + @marketo = Grabcad::Marketo.create_client(ACCESS_KEY, SECRET_KEY, ENDPOINT) + @marketo.logger=(Logger.new(STDOUT)) + @marketo.log_level = Logger::DEBUG + @lead = @marketo.get_lead_by_lead_id(IDNUM) end - context "list operations" do - LIST_KEY = 'awesome leads list' + it "should check if a lead is a member of a list and return false" do + fixture = File.read("spec/fixtures/is_lead_in_list_false.xml") + savon.expects(:list_operation).with(message: + {:list_operation=>"ISMEMBEROFLIST", + :list_key=>{:key_type=>"MKTOLISTNAME", + :key_value=>TEST_LIST}, + :strict=>"false", + :list_member_list=>{:lead_key=>[{:key_type=>"IDNUM", :key_value=>IDNUM}]}}).returns(fixture) - before(:each) do - @savon_client = mock('savon_client') - @authentication_header = mock('authentication_header') - @client = Rapleaf::Marketo::Client.new(@savon_client, @authentication_header) - end - - it "should have the correct body format on add_to_list" do - response_hash = {} # TODO - expect_request(@savon_client, - @authentication_header, - equals_matcher({ - :list_operation => ListOperationType::ADD_TO, - :list_key => LIST_KEY, - :strict => 'false', - :list_member_list => { - :lead_key => [ - { - :key_type => 'EMAIL', - :key_value => EMAIL - } - ] - } - }), - 'ns1:paramsListOperation', - response_hash) + @marketo.is_member_of_list?( TEST_LIST, @lead).should be false + end - @client.add_to_list(LIST_KEY, EMAIL).should == response_hash - end + it "should check if a lead is a member of a list and return true" do + fixture = File.read("spec/fixtures/is_lead_in_list_true.xml") + savon.expects(:list_operation).with(message: + {:list_operation=>"ISMEMBEROFLIST", + :list_key=>{:key_type=>"MKTOLISTNAME", + :key_value=>TEST_LIST}, + :strict=>"false", + :list_member_list=>{:lead_key=>[{:key_type=>"IDNUM", :key_value=>IDNUM}]}}).returns(fixture) - it "should have the correct body format on remove_from_list" do - response_hash = {} # TODO - expect_request(@savon_client, - @authentication_header, - equals_matcher({ - :list_operation => ListOperationType::REMOVE_FROM, - :list_key => LIST_KEY, - :strict => 'false', - :list_member_list => { - :lead_key => [ - { - :key_type => 'EMAIL', - :key_value => EMAIL - } - ] - } - }), - 'ns1:paramsListOperation', - response_hash) + @marketo.is_member_of_list?( TEST_LIST, @lead).should be true + end - @client.remove_from_list(LIST_KEY, EMAIL).should == response_hash - end + it "should add a lead to a list" do + fixture = File.read("spec/fixtures/add_lead_to_list.xml") + savon.expects(:list_operation).with(message: + {:list_operation=>"ADDTOLIST", + :list_key=>{:key_type=>"MKTOLISTNAME", + :key_value=>TEST_LIST}, + :strict=>"false", + :list_member_list=>{:lead_key=>[{:key_type=>"IDNUM", :key_value=>IDNUM}]}}).returns(fixture) - it "should have the correct body format on is_member_of_list?" do - response_hash = {} # TODO - expect_request(@savon_client, - @authentication_header, - equals_matcher({ - :list_operation => ListOperationType::IS_MEMBER_OF, - :list_key => LIST_KEY, - :strict => 'false', - :list_member_list => { - :lead_key => [ - { - :key_type => 'EMAIL', - :key_value => EMAIL - } - ] - } - }), - 'ns1:paramsListOperation', - response_hash) - @client.is_member_of_list?(LIST_KEY, EMAIL).should == response_hash - end + @marketo.add_to_list(TEST_LIST, @lead).should be true end - end - private + it "should remove a lead from a list" do + fixture = File.read("spec/fixtures/remove_lead_from_list.xml") + savon.expects(:list_operation).with(message: + {:list_operation=>"REMOVEFROMLIST", + :list_key=>{:key_type=>"MKTOLISTNAME", + :key_value=>TEST_LIST}, + :strict=>"false", + :list_member_list=>{:lead_key=>[{:key_type=>"IDNUM", :key_value=>IDNUM}]}}).returns(fixture) - def equals_matcher(expected) - Proc.new { |actual| - actual.should == expected - } - end - - def expect_request(savon_client, authentication_header, expected_body_matcher, expected_namespace, response_hash) - header_hash = stub('header_hash') - soap_response = stub('soap_response') - request_namespace = mock('namespace') - request_header = mock('request_header') - soap_request = mock('soap_request') - authentication_header.should_receive(:set_time) - authentication_header.should_receive(:to_hash).and_return(header_hash) - request_namespace.should_receive(:[]=).with("xmlns:ns1", "http://www.marketo.com/mktows/") - request_header.should_receive(:[]=).with("ns1:AuthenticationHeader", header_hash) - soap_request.should_receive(:namespaces).and_return(request_namespace) - soap_request.should_receive(:header).and_return(request_header) - soap_request.should_receive(:body=) do |actual_body| - expected_body_matcher.call(actual_body) + @marketo.remove_from_list(TEST_LIST, @lead).should be true end - soap_response.should_receive(:to_hash).and_return(response_hash) - savon_client.should_receive(:request).with(expected_namespace).and_yield(soap_request).and_return(soap_response) - end - end - - describe ListOperationType do - it 'should define the correct types' do - ListOperationType::ADD_TO.should == 'ADDTOLIST' - ListOperationType::IS_MEMBER_OF.should == 'ISMEMBEROFLIST' - ListOperationType::REMOVE_FROM.should == 'REMOVEFROMLIST' end end end -end \ No newline at end of file +end diff --git a/spec/marketo/key_spec.rb b/spec/marketo/key_spec.rb new file mode 100644 index 0000000..2e5ec96 --- /dev/null +++ b/spec/marketo/key_spec.rb @@ -0,0 +1,29 @@ +require File.expand_path('../spec_helper', File.dirname(__FILE__)) + +module Grabcad + module Marketo + + + describe Key do + + TEST_KEY_VALUE = 'a value' + TEST_KEY_TYPE = "type" + + it "should store type and value on construction" do + + key = Key.new(TEST_KEY_TYPE, TEST_KEY_VALUE) + key.key_type.should == TEST_KEY_TYPE + key.key_value.should == TEST_KEY_VALUE + end + + it "should to_hash correctly" do + key = Key.new(TEST_KEY_TYPE, TEST_KEY_VALUE) + + key.to_hash.should == { + :key_type => TEST_KEY_TYPE, + :key_value => TEST_KEY_VALUE + } + end + end + end +end diff --git a/spec/marketo/lead_key_spec.rb b/spec/marketo/lead_key_spec.rb index ba2f146..ad69def 100644 --- a/spec/marketo/lead_key_spec.rb +++ b/spec/marketo/lead_key_spec.rb @@ -1,6 +1,6 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) -module Rapleaf +module Grabcad module Marketo describe LeadKeyType do it "should define the correct types" do @@ -16,23 +16,24 @@ module Marketo end end + describe LeadKey do + TEST_LEAD_KEY_VALUE = 'a value' + TEST_LEAD_KEY_TYPE = "test key" + it "should store type and value on construction" do - KEY_VALUE = 'a value' - KEY_TYPE = LeadKeyType::IDNUM - lead_key = LeadKey.new(KEY_TYPE, KEY_VALUE) - lead_key.key_type.should == KEY_TYPE - lead_key.key_value.should == KEY_VALUE + + lead_key = LeadKey.new(TEST_LEAD_KEY_TYPE, TEST_KEY_VALUE) + lead_key.key_type.should == TEST_LEAD_KEY_TYPE + lead_key.key_value.should == TEST_LEAD_KEY_VALUE end it "should to_hash correctly" do - KEY_VALUE = 'a value' - KEY_TYPE = LeadKeyType::IDNUM - lead_key = LeadKey.new(KEY_TYPE, KEY_VALUE) + lead_key = LeadKey.new(TEST_LEAD_KEY_TYPE, TEST_LEAD_KEY_VALUE) lead_key.to_hash.should == { - :key_type => KEY_TYPE, - :key_value => KEY_VALUE + :key_type => TEST_LEAD_KEY_TYPE, + :key_value => TEST_LEAD_KEY_VALUE } end end diff --git a/spec/marketo/lead_record_spec.rb b/spec/marketo/lead_record_spec.rb index c616fed..8f56854 100644 --- a/spec/marketo/lead_record_spec.rb +++ b/spec/marketo/lead_record_spec.rb @@ -1,26 +1,26 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) -module Rapleaf +module Grabcad module Marketo - EMAIL = 'some@email.com' - IDNUM = 93480938 - + TEST_EMAIL = 'some@email.com' + TEST_IDNUM = 93480938 describe LeadRecord do + let (:marketo_client) {double} it "should store the idnum" do - lead_record = LeadRecord.new(EMAIL, IDNUM) - lead_record.idnum.should == IDNUM + lead_record = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) + lead_record.id.should == TEST_IDNUM end it "should store the email" do - LeadRecord.new(EMAIL, IDNUM).email.should == EMAIL + LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM).email.should == TEST_EMAIL end it "should implement == sensibly" do - lead_record1 = LeadRecord.new(EMAIL, IDNUM) + lead_record1 = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) lead_record1.set_attribute('favourite color', 'red') lead_record1.set_attribute('age', '100') - lead_record2 = LeadRecord.new(EMAIL, IDNUM) + lead_record2 = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) lead_record2.set_attribute('favourite color', 'red') lead_record2.set_attribute('age', '100') @@ -28,20 +28,20 @@ module Marketo end it "should store when attributes are set" do - lead_record = LeadRecord.new(EMAIL, IDNUM) + lead_record = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) lead_record.set_attribute('favourite color', 'red') lead_record.get_attribute('favourite color').should == 'red' end it "should store when attributes are updated" do - lead_record = LeadRecord.new(EMAIL, IDNUM) + lead_record = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) lead_record.set_attribute('favourite color', 'red') lead_record.set_attribute('favourite color', 'green') lead_record.get_attribute('favourite color').should == 'green' end it "should yield all attributes through each_attribute_pair" do - lead_record = LeadRecord.new(EMAIL, IDNUM) + lead_record = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) lead_record.set_attribute('favourite color', 'red') lead_record.set_attribute('favourite color', 'green') lead_record.set_attribute('age', '99') @@ -54,12 +54,12 @@ module Marketo pairs.size.should == 3 pairs.should include(['favourite color', 'green']) pairs.should include(['age', '99']) - pairs.should include(['Email', EMAIL]) + pairs.should include(['Email', TEST_EMAIL]) end it "should be instantiable from a savon hash" do savon_hash = { - :email => EMAIL, + :email => TEST_EMAIL, :foreign_sys_type => nil, :lead_attribute_list => { :attribute => [ @@ -69,18 +69,38 @@ module Marketo ] }, :foreign_sys_person_id => nil, - :id => IDNUM + :id => TEST_IDNUM } - actual = LeadRecord.from_hash(savon_hash) + actual = LeadRecord.from_hash(marketo_client, savon_hash) - expected = LeadRecord.new(EMAIL, IDNUM) + expected = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) expected.set_attribute('Company', 'Rapleaf') expected.set_attribute('FirstName', 'James') expected.set_attribute('LastName', 'O\'Brien') actual.should == expected end + + it "sync should call client to sync via ID with marketo backend" do + savon_hash = { + :email => TEST_EMAIL, + :foreign_sys_type => nil, + :lead_attribute_list => { + :attribute => [ + { :attr_name => 'Company', :attr_type => 'string', :attr_value => 'Rapleaf'}, + { :attr_name => 'FirstName', :attr_type => 'string', :attr_value => 'James'}, + { :attr_name => 'LastName', :attr_type => 'string', :attr_value => 'O\'Brien'} + ] + }, + :foreign_sys_person_id => nil, + :id => TEST_IDNUM + } + marketo_client.should_receive(:sync_lead_record_by_id).with(anything).and_return(savon_hash) + lead_record = LeadRecord.new(marketo_client, TEST_EMAIL, TEST_IDNUM) + lead_record.sync + + end end end end \ No newline at end of file diff --git a/spec/marketo/list_key_spec.rb b/spec/marketo/list_key_spec.rb new file mode 100644 index 0000000..d431dea --- /dev/null +++ b/spec/marketo/list_key_spec.rb @@ -0,0 +1,35 @@ +require File.expand_path('../spec_helper', File.dirname(__FILE__)) + +module Grabcad + module Marketo + describe ListKeyType do + it "should define the correct types" do + ListKeyType::MKTOLISTNAME.should == 'MKTOLISTNAME' + ListKeyType::MKTOSALESUSERID.should == 'MKTOSALESUSERID' + ListKeyType::SFDCLEADOWNERID.should == 'SFDCLEADOWNERID' + + end + end + + describe ListKey do + TEST_LIST_KEY_VALUE = 'a value' + TEST_LIST_KEY_TYPE = "key type" + + it "should store type and value on construction" do + + lead_key = ListKey.new(TEST_LIST_KEY_TYPE, TEST_LIST_KEY_VALUE) + lead_key.key_type.should == TEST_LIST_KEY_TYPE + lead_key.key_value.should == TEST_LIST_KEY_VALUE + end + + it "should to_hash correctly" do + lead_key = ListKey.new(TEST_LIST_KEY_TYPE, TEST_LIST_KEY_VALUE) + + lead_key.to_hash.should == { + :key_type => TEST_LIST_KEY_TYPE, + :key_value => TEST_LIST_KEY_VALUE + } + end + end + end +end