leap_salesforce 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|