leap_salesforce 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.idea/dictionaries/iqa.xml +9 -0
- data/.idea/leap-salesforce.iml +93 -0
- data/.idea/markdown-navigator/profiles_settings.xml +3 -0
- data/.idea/markdown-navigator.xml +86 -0
- data/.idea/misc.xml +12 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +992 -0
- data/.leap_salesforce.yml +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +56 -0
- data/Rakefile +21 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/exe/leap-salesforce +7 -0
- data/leap_salesforce.gemspec +43 -0
- data/lib/leap_salesforce/ext/string.rb +34 -0
- data/lib/leap_salesforce/ext/time.rb +15 -0
- data/lib/leap_salesforce/generator/default_fields.rb +13 -0
- data/lib/leap_salesforce/generator/soql_enums.rb +46 -0
- data/lib/leap_salesforce/generator/soql_objects.rb +97 -0
- data/lib/leap_salesforce/generator/templates/factory.rb.erb +12 -0
- data/lib/leap_salesforce/generator/templates/picklist.rb.erb +30 -0
- data/lib/leap_salesforce/generator/templates/soql_object.rb.erb +7 -0
- data/lib/leap_salesforce/generator/templates/soql_object_field_names.rb.erb +10 -0
- data/lib/leap_salesforce/limits.rb +13 -0
- data/lib/leap_salesforce/parameters.rb +53 -0
- data/lib/leap_salesforce/rake/setup.rake +25 -0
- data/lib/leap_salesforce/rake.rb +5 -0
- data/lib/leap_salesforce/soql_data/data_relationships.rb +35 -0
- data/lib/leap_salesforce/soql_data/meta_data_handler.rb +11 -0
- data/lib/leap_salesforce/soql_data/soql_data.rb +173 -0
- data/lib/leap_salesforce/soql_data/soql_enum.rb +8 -0
- data/lib/leap_salesforce/soql_data/soql_global_data.rb +26 -0
- data/lib/leap_salesforce/soql_data/soql_global_object_data.rb +225 -0
- data/lib/leap_salesforce/soql_data/soql_handler.rb +39 -0
- data/lib/leap_salesforce/soql_data/soql_object_describe.rb +100 -0
- data/lib/leap_salesforce/soql_data/soql_settings.rb +31 -0
- data/lib/leap_salesforce/users/user.rb +27 -0
- data/lib/leap_salesforce/users/users.rb +25 -0
- data/lib/leap_salesforce/version.rb +5 -0
- data/lib/leap_salesforce.rb +63 -0
- metadata +292 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Represents common relationships from a Data class
|
4
|
+
module DataRelationships
|
5
|
+
# @example
|
6
|
+
# # Retrieve organisation record associated to an opportunity and then get its name
|
7
|
+
# Opportunity.organisation.name
|
8
|
+
# Retrieve organisation related to current opportunity
|
9
|
+
def organisation
|
10
|
+
raise '"Organisation" class not yet defined' unless defined? Organisation
|
11
|
+
|
12
|
+
Organisation.get(Id: self['AccountId'])
|
13
|
+
end
|
14
|
+
|
15
|
+
# @example Get user name
|
16
|
+
# record.owner.name
|
17
|
+
# @return [Exchange] object representing owner of object
|
18
|
+
def owner
|
19
|
+
User.get(Id: self[:owner_id])
|
20
|
+
end
|
21
|
+
|
22
|
+
# @example Get user name
|
23
|
+
# record.queue.name
|
24
|
+
# @return [Exchange] object representing owner of object
|
25
|
+
def queue
|
26
|
+
Group.get(Id: self[:owner_id])
|
27
|
+
end
|
28
|
+
|
29
|
+
# Retrieve record type for current object
|
30
|
+
def record_type
|
31
|
+
raise '"RecordType" class not yet defined' unless defined? RecordType
|
32
|
+
|
33
|
+
RecordType.get(Id: self['RecordTypeId'])
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Handles tests interaction and verification based upon Metadata
|
4
|
+
class MetaDataHandler
|
5
|
+
@objects_to_verify = []
|
6
|
+
class << self
|
7
|
+
# @return [Array] List of objects to verify metadata for. This includes enums, required values
|
8
|
+
# Changes to these values will need to be version controlled
|
9
|
+
attr_accessor :objects_to_verify
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'meta_data_handler'
|
4
|
+
require_relative 'soql_handler'
|
5
|
+
require_relative 'soql_global_data'
|
6
|
+
require_relative 'data_relationships'
|
7
|
+
require_relative 'soql_global_object_data'
|
8
|
+
require_relative 'soql_object_describe'
|
9
|
+
require_relative 'soql_enum'
|
10
|
+
require_relative 'soql_settings'
|
11
|
+
|
12
|
+
# Represents an API interaction via SOQL queries with the Salesforce database
|
13
|
+
class SoqlData < Exchange
|
14
|
+
include DataRelationships
|
15
|
+
extend SoqlGlobalData
|
16
|
+
extend SoqlGlobalObjectData
|
17
|
+
extend SoqlObjectDescribe
|
18
|
+
extend SoqlSettings
|
19
|
+
|
20
|
+
# Create a new SoqlData object. If override_parameters are empty then it's assumed creation of
|
21
|
+
# an object is being done
|
22
|
+
#
|
23
|
+
# @example Create a new Contact step by step (Contact inherits from this class)
|
24
|
+
# contact = Contact.new
|
25
|
+
# contact.first_name = 'Bob'
|
26
|
+
# contact.last_name = 'Smith'
|
27
|
+
# contact.save! # API request made at this point
|
28
|
+
#
|
29
|
+
# @example Create a contact using a factory with a trait of :email
|
30
|
+
# contact_with_email = FactoryBot.create(:contact, :email)
|
31
|
+
#
|
32
|
+
# @example Perform a get to the Salesforce limits api
|
33
|
+
# SoqlData.new("Limits", method: :get, suburl: 'limits/')
|
34
|
+
#
|
35
|
+
# @param [String] name Name describing object. Defaults to itself
|
36
|
+
# @param [Hash] http_parameters Parameters used in making HTTP request. If creating a new object
|
37
|
+
# leave this to empty. Otherwise Hash would look like method: :get, suburl: 'URL_AFTER_SOQL_HANDLER_BASE_URL'
|
38
|
+
def initialize(name = nil, http_parameters = {})
|
39
|
+
super
|
40
|
+
|
41
|
+
return unless http_parameters.empty?
|
42
|
+
|
43
|
+
table_name = self.class.soql_object_name
|
44
|
+
self.suburl = "sobjects/#{table_name}"
|
45
|
+
optional_name = self.class.to_s == table_name ? '' : "(#{table_name})"
|
46
|
+
self.test_name = "Factory for '#{self.class}'#{optional_name}" unless name
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] User used to make api calls
|
50
|
+
attr_accessor :api_user
|
51
|
+
|
52
|
+
expect_positive_status # Retry upon failure for successful status code
|
53
|
+
|
54
|
+
# Api username below references stored variable through ERB so this can be changed at run time (otherwise user would be fixed)
|
55
|
+
default_handler SoqlHandler, 'Factory', api_user: '<%= LeapSalesforce.api_user %>'
|
56
|
+
@ids_to_delete = {}
|
57
|
+
@api_user = LeapSalesforce.api_user
|
58
|
+
|
59
|
+
# Unsets the element so it's not set in the request at all (not just not setting it to empty)
|
60
|
+
# @param [String, Symbol] element_to_unset Element to remove from being sent
|
61
|
+
def unset=(element_to_unset)
|
62
|
+
@override_parameters[:body].delete element_to_unset.to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
# Extract the id or return the cached version of it
|
66
|
+
# @return [String] Id of Salesforce Object
|
67
|
+
def id
|
68
|
+
@id ||= self['$..id,$..Id']
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param [Boolean] set Whether to not retry for successful response (Used when you expect an error)
|
72
|
+
def no_retry=(set)
|
73
|
+
return unless set
|
74
|
+
|
75
|
+
# Set retry_count to 0 so if an invalid status code is returned a retry will not occur
|
76
|
+
define_singleton_method('retry_count') { 0 }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get details of itself by searching for it's id
|
80
|
+
# Store response within itself
|
81
|
+
# @return [Exchange] Exchange with details of data
|
82
|
+
def get
|
83
|
+
@response = self.class.get(Id: id).response # Make get call and store result
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Update current record with data provided
|
88
|
+
# @param [Hash] data Data to update exchange with
|
89
|
+
def update(data)
|
90
|
+
self.class.update(id, data)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delete current record
|
94
|
+
# @param [Boolean] must_pass Whether to raise exception if call is not successful
|
95
|
+
# @return [Exchange] Exchange object making delete
|
96
|
+
def delete(must_pass: false)
|
97
|
+
self.class.delete(id, must_pass: must_pass)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Delete current record, switching to Admin before doing so
|
101
|
+
# @param [Boolean] must_pass Whether to raise exception if call is not successful
|
102
|
+
# @return [Exchange] Exchange object making delete
|
103
|
+
def delete_as_admin(must_pass: true)
|
104
|
+
LeapSalesforce.api_username = Test.email_for('System Admin')
|
105
|
+
delete must_pass: must_pass
|
106
|
+
end
|
107
|
+
|
108
|
+
# Update current record with data provided expecting success
|
109
|
+
# @param [Hash] data Data to update exchange with
|
110
|
+
def success_update(data)
|
111
|
+
update(**data, must_pass: true)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Array] List of ids from response
|
115
|
+
def ids
|
116
|
+
values_from_path('$..Id')
|
117
|
+
end
|
118
|
+
|
119
|
+
# Set a parameter request in the request body.
|
120
|
+
# Can be used to build a request over several steps (e.g Cucumber)
|
121
|
+
# Will be used with FactoryBot
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# exchange['name'] = 'tester'
|
125
|
+
# # Will set { name: tester } in the response, formatting as JSON or XML depending on REST / SOAP
|
126
|
+
# @param [String, Symbol] key Name of request element to set
|
127
|
+
# @param [String] value Value to set request element to
|
128
|
+
def []=(key, value)
|
129
|
+
@override_parameters[:body] ||= {}
|
130
|
+
value = value.salesforce_format if value.is_a? Time
|
131
|
+
@override_parameters[:body][key] = value
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String] Error message if present
|
135
|
+
def diagnose_error
|
136
|
+
return error_message if error_message?
|
137
|
+
|
138
|
+
return response if @response # If response is made it would be helpful in diagnosing
|
139
|
+
|
140
|
+
inspect # If no response, this may help
|
141
|
+
end
|
142
|
+
|
143
|
+
# @return [Boolean] Whether error message element is present
|
144
|
+
def error_message?
|
145
|
+
error_message_element?
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [String] Error message if present. If not an error is raised
|
149
|
+
def error_message
|
150
|
+
if error_message?
|
151
|
+
self[:message]
|
152
|
+
else
|
153
|
+
message = "No error message received. Status code is #{status_code}. "
|
154
|
+
message += 'Response is successful when it should not be. ' if status_code.to_s[0] == '2'
|
155
|
+
message += 'Response is empty' if response.to_s.empty?
|
156
|
+
raise LeapSalesforce::ResponseError, message
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# @return [True, LeapSalesforce::ResponseError] Whether response is successful
|
162
|
+
def successful?
|
163
|
+
raise LeapSalesforce::ResponseError, "Error with updating #{self} #{diagnose_error}" unless (200..299).cover? status_code
|
164
|
+
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns descendants of the provided class SoqlData
|
169
|
+
# @return [Class] Classes that inherit from this class
|
170
|
+
def self.descendants
|
171
|
+
ObjectSpace.each_object(Class).select { |class_name| class_name < self }
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Global Methods to interact with Soql Data
|
4
|
+
module SoqlGlobalData
|
5
|
+
attr_accessor :ids_to_delete
|
6
|
+
|
7
|
+
# Return limits of Salesforce org. https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_limits.htm
|
8
|
+
# @example See Conga limits
|
9
|
+
# SoqlData.limits["['Conga Composer - EU']"]
|
10
|
+
# @return [Exchange] Limits of Salesforce ORG
|
11
|
+
def limits
|
12
|
+
@limits ||= new("#{self} limits", method: :get, suburl: 'limits/')
|
13
|
+
end
|
14
|
+
|
15
|
+
# Describe all salesforce objects (this returns BIG response)
|
16
|
+
# See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_describeGlobal.htm for reference
|
17
|
+
# @example Count how many sobjects there are
|
18
|
+
# objects = SoqlData.global_describe['sobjects']
|
19
|
+
# objects.size
|
20
|
+
# @example Find description of object with label Organisation
|
21
|
+
# org_desc = SoqlData.global_describe['sobjects'].find { |obj| obj['label'] == 'Organisation' }
|
22
|
+
# @return [Exchange] Global describe of salesforce Objects
|
23
|
+
def global_describe
|
24
|
+
@global_describe ||= new('Global describe of sobjects', method: :get, suburl: 'sobjects/')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Methods for working with instances of global to soql objects, not global overall
|
4
|
+
# See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm
|
5
|
+
module SoqlGlobalObjectData
|
6
|
+
# @return [Hash] List of ids to delete when deleting a particular object
|
7
|
+
attr_accessor :ids_to_delete
|
8
|
+
|
9
|
+
# Override to handle removing dependent records
|
10
|
+
def remove_dependent_records(_id); end
|
11
|
+
|
12
|
+
# Return key and value to look up for a provided hash
|
13
|
+
# @return [Array] Array with [column_to_lookup, value_to_look_for]
|
14
|
+
def extract_lookup(lookup)
|
15
|
+
raise 'Need to pass to Soql object key value pair to look up by' unless lookup.first.is_a? Array
|
16
|
+
|
17
|
+
lookup_key, lookup_value = lookup.first
|
18
|
+
raise 'Need to set lookup_key' unless lookup_key
|
19
|
+
|
20
|
+
[lookup_key, lookup_value]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Url enconding needs to be used when searching for special characters (+ => '%2B')
|
24
|
+
# (see https://www.w3schools.com/tags/ref_urlencode.asp)
|
25
|
+
# @param [String] soql_query String representing SOQL query
|
26
|
+
# @param [Boolean] wait Whether to wait for record if no result returned
|
27
|
+
# @example
|
28
|
+
# query SELECT Name from Account WHERE Name = 'TEST Org 001'
|
29
|
+
# This is converted to:
|
30
|
+
# perform_query "SELECT+Name+from+Account+WHERE+Name+=+'TEST+Org+001'"
|
31
|
+
# @return [Exchange] Exchange object from which JSON response can be obtained (i.e, with exchange.response)
|
32
|
+
def query(soql_query, wait: true)
|
33
|
+
rest_query = soql_query.gsub('%', '%25').gsub('+', '%2B').tr(' ', '+')
|
34
|
+
if wait
|
35
|
+
new("SOQL Query: #{soql_query}", method: :get, suburl: "query/?q=#{rest_query}").until(timeout: 20, interval: 1) do
|
36
|
+
response.body.include? '"url"' # Could be waiting for element to be created
|
37
|
+
end
|
38
|
+
else
|
39
|
+
new("SOQL Query: #{soql_query}", method: :get, suburl: "query/?q=#{rest_query}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Symbol, String] lookup Hash representing look up performed
|
44
|
+
# @param [String] url Url to get
|
45
|
+
def data_from_url(url, lookup)
|
46
|
+
new("Id at #{url}", method: :get, suburl: url.split("v#{SoqlHandler.api_version}/").last)
|
47
|
+
rescue NoElementAtPath
|
48
|
+
raise NoElementAtPath, "No result found for #{lookup} under user #{LeapSalesforce.api_user}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# For dates (ending with .000Z), query is always greater than
|
52
|
+
# @param [Hash] lookup Hash to look up values according to
|
53
|
+
# @return [String] SOQL query to filter results
|
54
|
+
def soql_lookup_filter(lookup)
|
55
|
+
limit = lookup.delete(:limit)
|
56
|
+
conditional = ''
|
57
|
+
lookup.each do |key, value|
|
58
|
+
conditional_term = conditional.empty? ? 'WHERE' : 'AND'
|
59
|
+
conditional += "#{conditional_term} #{key} #{condition_for(value)} "
|
60
|
+
end
|
61
|
+
query = conditional + 'ORDER BY CreatedDate DESC NULLS FIRST'
|
62
|
+
query += " LIMIT #{limit}" if limit
|
63
|
+
query
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the response data for a SoqlObject using the calling class's table.
|
67
|
+
# Will get the latest created date.
|
68
|
+
# @example
|
69
|
+
# # Get a contact where LastName is 'Bob'
|
70
|
+
# Contact.get(LastName: 'Bob')
|
71
|
+
# @param [Hash] lookup Key value pair unique to Salesforce to query for
|
72
|
+
# @option lookup [Boolean] :teardown Whether to remove id after scenario finished
|
73
|
+
# @return [SoqlData]
|
74
|
+
def get(lookup)
|
75
|
+
teardown = lookup.delete(:teardown)
|
76
|
+
SoqlHandler.new("Query on #{self}").use
|
77
|
+
instance_to_get = if lookup.key? :Id
|
78
|
+
new("Lookup id: #{lookup[:Id]}", method: :get, suburl: "sobjects/#{soql_object_name}/#{lookup[:Id]}")
|
79
|
+
else
|
80
|
+
initial_query = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}"
|
81
|
+
data_from_url initial_query['$..url'], lookup
|
82
|
+
end
|
83
|
+
SoqlData.ids_to_delete[self] = instance_to_get[:id] if teardown
|
84
|
+
instance_to_get
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Exchange] Result of looking up id based on lookup criteria
|
88
|
+
def lookup_id(lookup)
|
89
|
+
teardown = lookup.delete(:teardown)
|
90
|
+
SoqlHandler.new("Query on #{self}").use
|
91
|
+
result = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}", wait: false
|
92
|
+
SoqlData.ids_to_delete[self] = id if teardown
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] Id that matches filter
|
97
|
+
def id_where(lookup)
|
98
|
+
lookup_id(lookup).id
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean] Whether any result for lookup
|
102
|
+
def any_where?(lookup)
|
103
|
+
lookup_id(lookup)[:totalSize] != 0
|
104
|
+
end
|
105
|
+
|
106
|
+
# Perform the code in the block for all the ids matching a query.
|
107
|
+
# If no block, return a list of objects
|
108
|
+
# @param [Hash] lookup Key value pair unique to Salesforce to query for
|
109
|
+
# @yield [id] Perform block for each id returned. The 'id' parameter in a block represents an id matching the query
|
110
|
+
# @return [Array] List of ids matching criteria. Only used if no block given
|
111
|
+
def each_id_with(lookup)
|
112
|
+
lookup[:limit] ||= nil # Don't limit results returned
|
113
|
+
SoqlHandler.new("Each Id where #{self}").use
|
114
|
+
results = query "SELECT Id FROM #{soql_object_name} #{soql_lookup_filter(lookup)}", wait: false
|
115
|
+
ids = results.ids
|
116
|
+
if block_given?
|
117
|
+
ids.each { |id| yield(id) }
|
118
|
+
else
|
119
|
+
ids
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param [Hash] lookup Key value pair unique to Salesforce to query for
|
124
|
+
# @return [Array] List of Soql objects matching criteria
|
125
|
+
def each_with(lookup)
|
126
|
+
ids = each_id_with lookup
|
127
|
+
ids.collect { |id| get(Id: id) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove all ids from table that match lookup criteria
|
131
|
+
# @param [Hash] lookup Key value pair unique to Salesforce to query for
|
132
|
+
def delete_ids_with(lookup)
|
133
|
+
each_id_with(lookup, &method(:delete))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Remove object from Salesforce with provided id
|
137
|
+
# @param [String] id Id of element to update
|
138
|
+
# @param [Hash] data Key value pairs with data to update
|
139
|
+
# @return [Exchange] Exchange object for object
|
140
|
+
def update(id, data)
|
141
|
+
must_pass = data.delete(:must_pass)
|
142
|
+
data = data.transform_values do |value|
|
143
|
+
value.is_a?(Time) ? value.salesforce_format : value
|
144
|
+
end
|
145
|
+
SoqlHandler.new("Update #{id}").use
|
146
|
+
|
147
|
+
update = new("Update #{self}, #{id} with '#{data}'", method: :patch,
|
148
|
+
suburl: "sobjects/#{soql_object_name}/#{id}", body: data)
|
149
|
+
update.call
|
150
|
+
|
151
|
+
return update unless must_pass
|
152
|
+
|
153
|
+
successful?
|
154
|
+
update
|
155
|
+
end
|
156
|
+
|
157
|
+
# Remove object from Salesforce with provided id
|
158
|
+
# @param [String] id Id of element to remove
|
159
|
+
# @param [Boolean] must_pass Whether to raise exception if call is not successful
|
160
|
+
# @return [Exchange] Exchange object making delete call
|
161
|
+
def delete(id, must_pass: false)
|
162
|
+
SoqlData.ids_to_delete.reject! { |table, id_to_remove| table == self && id_to_remove == id } # Remove id from list to delete
|
163
|
+
remove_dependent_records(id)
|
164
|
+
|
165
|
+
SoqlHandler.new("Delete #{id}").use
|
166
|
+
delete = new('SOQL Delete', method: :delete, suburl: "sobjects/#{soql_object_name}/#{id}")
|
167
|
+
delete.call
|
168
|
+
return delete unless must_pass
|
169
|
+
|
170
|
+
successful?
|
171
|
+
delete
|
172
|
+
end
|
173
|
+
|
174
|
+
# rubocop:disable Metrics/MethodLength
|
175
|
+
# rubocop:disable Metrics/AbcSize
|
176
|
+
def soql_element(name, backend_name)
|
177
|
+
# Either set the element (if creating a new record) or update the object
|
178
|
+
# @param [String] new_value Value to update record to
|
179
|
+
define_method("#{name}=") do |new_value|
|
180
|
+
if @response
|
181
|
+
@response = update(backend_name => new_value).response
|
182
|
+
else
|
183
|
+
self[backend_name] = new_value.class < SoqlData ? new_value.id : new_value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [String] Value of backend name
|
188
|
+
define_method name.to_s do
|
189
|
+
begin
|
190
|
+
self[backend_name]
|
191
|
+
rescue NoElementAtPath
|
192
|
+
raise diagnose_error if error_message?
|
193
|
+
|
194
|
+
@response = get.response
|
195
|
+
self[backend_name]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
# @return [String] Name of backend name for element
|
199
|
+
define_method("#{name}_element") { backend_name }
|
200
|
+
end
|
201
|
+
# rubocop:enable Metrics/MethodLength
|
202
|
+
# rubocop:enable Metrics/AbcSize
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
# @todo: Cover >= condition
|
207
|
+
# @param [String] value Value to search for. Certain
|
208
|
+
# @return [String] Condition criteria to match value
|
209
|
+
def condition_for(value)
|
210
|
+
operator = case value[0]
|
211
|
+
when '>' then '>'
|
212
|
+
when '<' then '<'
|
213
|
+
when '~' then 'LIKE'
|
214
|
+
else
|
215
|
+
return "= '#{value}'" unless value.type_of_time?
|
216
|
+
|
217
|
+
return "= #{value.to_zulu_date_string}"
|
218
|
+
end
|
219
|
+
|
220
|
+
value = value[1..-1]
|
221
|
+
return "#{operator} #{value.to_zulu_date_string}" if value.type_of_time?
|
222
|
+
|
223
|
+
"#{operator} '#{value}'"
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'soaspec'
|
4
|
+
|
5
|
+
# Handles basic Soql interactions.
|
6
|
+
# Credentials are stored either in 'config/credentials' folder or via environment variables
|
7
|
+
# To check out SOQL SCHEMA go to workbench.developerforce.com. Use Sandbox and login
|
8
|
+
class SoqlHandler < Soaspec::RestHandler
|
9
|
+
oauth2 username: '<%= LeapSalesforce.api_user %>', password: LeapSalesforce.password,
|
10
|
+
client_id: LeapSalesforce.client_id,
|
11
|
+
client_secret: LeapSalesforce.client_secret,
|
12
|
+
token_url: "https://#{LeapSalesforce.environment == 'prod' ? 'login' : 'test'}.salesforce.com/services/oauth2/token"
|
13
|
+
base_url '<%= instance_url %>/services/data/v<%= api_version %>/'
|
14
|
+
|
15
|
+
pascal_keys true
|
16
|
+
headers authorization: 'Bearer <%= access_token %>', content_type: 'application/json'
|
17
|
+
|
18
|
+
element :success, '$..success'
|
19
|
+
element :error_message_element, '$..message'
|
20
|
+
element :soql_fields, :fields
|
21
|
+
|
22
|
+
# @return [String] Version of Salesforce API to use
|
23
|
+
@api_version = '45.0'
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# @return [String] The current Salesforce instance URL obtained from oauth call
|
27
|
+
def instance_url
|
28
|
+
new.instance_url # Instance url is defined through oauth2_file method
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] Version of Salesforce API to use
|
32
|
+
attr_accessor :api_version
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] Version of Salesforce API to use
|
36
|
+
def api_version
|
37
|
+
SoqlHandler.api_version
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Methods relating to describing a soql object
|
4
|
+
# See https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm
|
5
|
+
# for reference.
|
6
|
+
# Note the data here is cached. If metadata changes you would need to re run Ruby Code to get updates if a call has
|
7
|
+
# already been made
|
8
|
+
module SoqlObjectDescribe
|
9
|
+
# Reference https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/sobject_describe_with_ifmodified_header.htm
|
10
|
+
# @todo Get this to work
|
11
|
+
def changes_from_date(_date)
|
12
|
+
@changes_from_date ||= new("describe #{self}", method: :get, suburl: "sobjects/#{soql_object_name}/describe/",
|
13
|
+
params: { if_modified_since: 'Wed, 3 Jul 2013 19:43:31 GMT' })
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [SoqlData] Retrieve JSON that describes current object
|
17
|
+
def description
|
18
|
+
@description ||= new("describe #{self}", method: :get, suburl: "sobjects/#{soql_object_name}/describe/")
|
19
|
+
end
|
20
|
+
|
21
|
+
def layouts
|
22
|
+
@layouts ||= new("layouts for #{self}",
|
23
|
+
method: :get, suburl: "sobjects/#{soql_object_name}/describe/layouts")
|
24
|
+
end
|
25
|
+
|
26
|
+
def layouts_for(record_type_id)
|
27
|
+
new("layouts for #{self} on #{record_type_id}",
|
28
|
+
method: :get, suburl: "sobjects/#{soql_object_name}/describe/layouts/#{record_type_id}")
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Array] List of fields that each are a hash of attributes
|
32
|
+
def fields
|
33
|
+
@fields ||= description[:fields]
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
37
|
+
# @return [Hash] Hash storing all properties of a field
|
38
|
+
def properties_for(field_name)
|
39
|
+
field_name = field_name.to_s
|
40
|
+
properties = fields.find { |field| %w[name label].any? { |label| field[label] == field_name } }
|
41
|
+
unless properties
|
42
|
+
raise LeapSalesforce::ResponseError, "Field name '#{field_name}' not found in '#{self}'" \
|
43
|
+
" using table name '#{soql_object_name}'. Field names are #{field_names}"
|
44
|
+
end
|
45
|
+
|
46
|
+
properties
|
47
|
+
end
|
48
|
+
|
49
|
+
# Finding Picklist values for specified fields
|
50
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
51
|
+
# @return [Array] List of values for passed in field_name
|
52
|
+
def picklist_for(field_name)
|
53
|
+
properties = properties_for field_name
|
54
|
+
|
55
|
+
properties['picklistValues'].collect { |list| list['label'] }
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
59
|
+
# @return [Integer] Max length of field
|
60
|
+
def length_for(field_name)
|
61
|
+
properties_for(field_name)['length']
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
65
|
+
# @return [String, nil] Default value for field provided
|
66
|
+
def default_for(field_name)
|
67
|
+
properties_for(field_name)['defaultValue']
|
68
|
+
end
|
69
|
+
|
70
|
+
def required
|
71
|
+
fields.find_all { |field| field[''] }
|
72
|
+
end
|
73
|
+
|
74
|
+
def picklists
|
75
|
+
label_names.find_all { |f| type_for(f) == 'picklist' }
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
79
|
+
# @return [String] Type of field (e.g., picklist, string, double, reference)
|
80
|
+
def type_for(field_name)
|
81
|
+
properties_for(field_name)['type']
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [String, Symbol] field_name Salesforce backend field name
|
85
|
+
# @return [String, nil] Other entity this field relates to if it's a reference field
|
86
|
+
def relationship_name_for(field_name)
|
87
|
+
properties_for(field_name)['relationshipName']
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [Array] Label names of object
|
91
|
+
def label_names
|
92
|
+
fields.collect { |field| field['label'] }
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Array]
|
96
|
+
# Field values for field names
|
97
|
+
def field_names
|
98
|
+
fields.collect { |field| field['name'] }
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Settings for configuring usage of Soql Objects
|
4
|
+
module SoqlSettings
|
5
|
+
# Set name of soql object. This is equivalent to the name in https://workbench.developerforce.com or
|
6
|
+
# the lightning URL when looking at an object
|
7
|
+
# @param [String] name Name of Soql object
|
8
|
+
def soql_object(name)
|
9
|
+
@soql_object_name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String] Name of Soql object
|
13
|
+
def soql_object_name
|
14
|
+
@soql_object_name || to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Whether to create enumerations for object when running generation tasks. By default this is true
|
18
|
+
# For some objects you may want to use them without tracking their enumerations
|
19
|
+
def create_enum(set)
|
20
|
+
@create_enum = set
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] Whether to create enum for object
|
24
|
+
def create_enum?
|
25
|
+
if @create_enum.nil?
|
26
|
+
true # True by default
|
27
|
+
else
|
28
|
+
@create_enum
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LeapSalesforce
|
4
|
+
# A test user used to execute tests
|
5
|
+
class User
|
6
|
+
# @return [String] Name to identify user by
|
7
|
+
attr_accessor :key
|
8
|
+
# @return [String] Username of User interpreted by ERB (in email address format). Use ERB to define users that can vary
|
9
|
+
# according to environment
|
10
|
+
attr_writer :username
|
11
|
+
# @return [String] Long form description of user. Could be used to reference them in a Cucumber test
|
12
|
+
attr_accessor :description
|
13
|
+
|
14
|
+
# @param [String, Symbol] key Key used to identify a test user
|
15
|
+
# @param [String] username Name used to login with user. In email address format.
|
16
|
+
def initialize(key, username, description: nil)
|
17
|
+
self.key = key
|
18
|
+
self.username = username
|
19
|
+
self.description = description
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String] Email address of User
|
23
|
+
def username
|
24
|
+
ERB.new(@username).result(binding)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|