leap_salesforce 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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