# frozen_string_literal: true require_relative 'meta_data_handler' require_relative 'soql_handler' require_relative 'soql_global_data' require_relative 'data_relationships' require_relative 'soql_global_object_data' require_relative 'soql_object_describe' require_relative 'soql_enum' require_relative 'soql_settings' # Represents an API interaction via SOQL queries with the Salesforce database class SoqlData < Exchange include DataRelationships extend SoqlGlobalData extend SoqlGlobalObjectData extend SoqlObjectDescribe extend SoqlSettings # Create a new SoqlData object. If override_parameters are empty then it's assumed creation of # an object is being done # # @example Create a new Contact step by step (Contact inherits from this class) # contact = Contact.new # contact.first_name = 'Bob' # contact.last_name = 'Smith' # contact.save! # API request made at this point # # @example Create a Contact by specifying field names # contact = Contact.new first_name: 'Bob', last_name: 'Smith' # contact.save! # # @example Create a contact using a factory with a trait of :email # contact_with_email = FactoryBot.create(:contact, :email) # # @example Perform a get to the Salesforce limits api # SoqlData.new("Limits", method: :get, suburl: 'limits/') # # @param [String, Hash] name Name describing object. Defaults to itself. If Hash is provided # with no 'http_parameters' then list will be fields to set on creating entity # @param [Hash] http_parameters Parameters used in making HTTP request. If creating a new object # leave this to empty. Otherwise Hash would look like method: :get, suburl: 'URL_AFTER_SOQL_HANDLER_BASE_URL' def initialize(name = nil, http_parameters = {}) super return unless http_parameters.empty? table_name = self.class.soql_object_name self.suburl = "sobjects/#{table_name}" optional_name = self.class.to_s == table_name ? '' : "(#{table_name})" self.test_name = "Factory for '#{self.class}'#{optional_name}" unless name return unless name.is_a? Hash name.each do |field_name, field_value| send("#{field_name}=", field_value) end end # Create object using FactoryBot # @param [Hash] factory_parameters Parameters to pass to FactoryBot def self.create(*factory_parameters) FactoryBot.create(to_s.snakecase.to_sym, *factory_parameters) end # @return [String] User used to make api calls attr_accessor :api_user expect_positive_status # Retry upon failure for successful status code # Api username below references stored variable through ERB so this can be changed at run time (otherwise user would be fixed) default_handler SoqlHandler, 'Factory', api_user: '<%= LeapSalesforce.api_user %>' @ids_to_delete = {} @api_user = LeapSalesforce.api_user # Unsets the element so it's not set in the request at all (not just not setting it to empty) # @param [String, Symbol] element_to_unset Element to remove from being sent def unset=(element_to_unset) @override_parameters[:body].delete element_to_unset.to_sym end # Extract the id or return the cached version of it # @return [String] Id of Salesforce Object def id @id ||= self['$..id,$..Id'] end # @param [Boolean] set Whether to not retry for successful response (Used when you expect an error) def no_retry=(set) return unless set # Set retry_count to 0 so if an invalid status code is returned a retry will not occur define_singleton_method('retry_count') { 0 } end # Get details of itself by searching for it's id # Store response within itself # @return [Exchange] Exchange with details of data def find @response = self.class.find(Id: id).response # Make get call and store result self end # @deprecated # Get details of itself by searching for it's id # Store response within itself # @return [Exchange] Exchange with details of data def get LeapSalesforce.logger.warn "Method 'get' called when it is deprecated" \ " from #{caller_locations[0]}" find end # Update current record with data provided # @param [Hash] data Data to update exchange with def update(data) self.class.update(id, data) end # Delete current record # @param [Boolean] must_pass Whether to raise exception if call is not successful # @return [Exchange] Exchange object making delete def delete(must_pass: false) self.class.delete(id, must_pass: must_pass) end # Delete current record, switching to Admin before doing so # @param [Boolean] must_pass Whether to raise exception if call is not successful # @return [Exchange] Exchange object making delete def delete_as_admin(must_pass: true) LeapSalesforce.api_username = Test.email_for('System Admin') delete must_pass: must_pass end # Update current record with data provided expecting success # @param [Hash] data Data to update exchange with def success_update(data) update(**data, must_pass: true) end # @return [Array] List of ids from response def ids values_from_path('$..Id') end # Add the passed in file as an attachment to the object def attach(filename) unless defined? Attachment raise LeapSalesforce::SetupError, 'Attachment not defined. ' \ "Add to '.leap_salesforce.yml' to use this" end raise LeapSalesforce::Error, "Filename #{filename} does not exist" unless File.exist? filename FactoryBot.create(:attachment, ParentId: id, Name: File.split(filename).last, body: Base64.encode64(File.read(filename))) end # Set a parameter request in the request body. # Can be used to build a request over several steps (e.g Cucumber) # Will be used with FactoryBot # # @example # exchange['name'] = 'tester' # # Will set { name: tester } in the response, formatting as JSON or XML depending on REST / SOAP # @param [String, Symbol] key Name of request element to set # @param [String] value Value to set request element to def []=(key, value) @override_parameters[:body] ||= {} value = value.salesforce_format if value.is_a? Time @override_parameters[:body][key] = value end # @return [String] Error message if present def diagnose_error return error_message if error_message? return response if @response # If response is made it would be helpful in diagnosing inspect # If no response, this may help end # @return [Boolean] Whether error message element is present def error_message? error_message_element? end # @return [String] Error message if present. If not an error is raised def error_message if error_message? self[:message] else message = "No error message received. Status code is #{status_code}. " message += 'Response is successful when it should not be. ' if status_code.to_s[0] == '2' message += 'Response is empty' if response.to_s.empty? raise LeapSalesforce::ResponseError, message end end # @return [True, LeapSalesforce::ResponseError] Whether response is successful def successful? raise LeapSalesforce::ResponseError, "Error with updating #{self} #{diagnose_error}" unless (200..299).cover? status_code true end # Returns descendants of the provided class SoqlData # @return [Class] Classes that inherit from this class def self.descendants ObjectSpace.each_object(Class).select { |class_name| class_name < self } end end