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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.idea/dictionaries/iqa.xml +9 -0
  4. data/.idea/leap-salesforce.iml +93 -0
  5. data/.idea/markdown-navigator/profiles_settings.xml +3 -0
  6. data/.idea/markdown-navigator.xml +86 -0
  7. data/.idea/misc.xml +12 -0
  8. data/.idea/modules.xml +8 -0
  9. data/.idea/vcs.xml +6 -0
  10. data/.idea/workspace.xml +992 -0
  11. data/.leap_salesforce.yml +10 -0
  12. data/.rspec +3 -0
  13. data/.rubocop.yml +3 -0
  14. data/CODE_OF_CONDUCT.md +74 -0
  15. data/Gemfile +6 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +56 -0
  18. data/Rakefile +21 -0
  19. data/bin/console +12 -0
  20. data/bin/setup +8 -0
  21. data/exe/leap-salesforce +7 -0
  22. data/leap_salesforce.gemspec +43 -0
  23. data/lib/leap_salesforce/ext/string.rb +34 -0
  24. data/lib/leap_salesforce/ext/time.rb +15 -0
  25. data/lib/leap_salesforce/generator/default_fields.rb +13 -0
  26. data/lib/leap_salesforce/generator/soql_enums.rb +46 -0
  27. data/lib/leap_salesforce/generator/soql_objects.rb +97 -0
  28. data/lib/leap_salesforce/generator/templates/factory.rb.erb +12 -0
  29. data/lib/leap_salesforce/generator/templates/picklist.rb.erb +30 -0
  30. data/lib/leap_salesforce/generator/templates/soql_object.rb.erb +7 -0
  31. data/lib/leap_salesforce/generator/templates/soql_object_field_names.rb.erb +10 -0
  32. data/lib/leap_salesforce/limits.rb +13 -0
  33. data/lib/leap_salesforce/parameters.rb +53 -0
  34. data/lib/leap_salesforce/rake/setup.rake +25 -0
  35. data/lib/leap_salesforce/rake.rb +5 -0
  36. data/lib/leap_salesforce/soql_data/data_relationships.rb +35 -0
  37. data/lib/leap_salesforce/soql_data/meta_data_handler.rb +11 -0
  38. data/lib/leap_salesforce/soql_data/soql_data.rb +173 -0
  39. data/lib/leap_salesforce/soql_data/soql_enum.rb +8 -0
  40. data/lib/leap_salesforce/soql_data/soql_global_data.rb +26 -0
  41. data/lib/leap_salesforce/soql_data/soql_global_object_data.rb +225 -0
  42. data/lib/leap_salesforce/soql_data/soql_handler.rb +39 -0
  43. data/lib/leap_salesforce/soql_data/soql_object_describe.rb +100 -0
  44. data/lib/leap_salesforce/soql_data/soql_settings.rb +31 -0
  45. data/lib/leap_salesforce/users/user.rb +27 -0
  46. data/lib/leap_salesforce/users/users.rb +25 -0
  47. data/lib/leap_salesforce/version.rb +5 -0
  48. data/lib/leap_salesforce.rb +63 -0
  49. 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module all SoqlEnums include
4
+ module SoqlEnum
5
+ def self.values_for(object)
6
+ ObjectSpace.each_object(Module).select { |class_name| class_name < self && class_name.to_s.start_with?("#{object}::") }
7
+ end
8
+ 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