# LeapSalesforce Welcome to LeapSalesforce gem. This gem helps ones to perform integration tests on Salesforce. It reads the Metadata from Salesforce and creates the foundation for API tests. Support for UI testing is being worked on in another gem [leap_salesforce_ui](https://gitlab.com/leap-dojo/leap_salesforce_ui). An example using this and the learnings it involved are in a Java Project [Leap Salesforce Java](https://gitlab.com/leap-dojo/leap_salesforce_java) ## Is this for you? This `gem` is an open source library aimed at making integrated test automation for Salesforce easy. Using it does require you to use code so if you really must have a `codeless` automation suite then you will need another tool (e.g., [Provar](provartesting.com)). However if you have some engineers (dev or test) who are keen to get their hands into some code this is worth trying. The benefits of an open source tool like this are: * Ease of use. Initialisation scripts make getting setup easy, code examples demonstrate how to perform common actions * Transparency. Nothing is hidden. Every piece of code can be seen by you * Flexibility. Using Ruby you are able to customize and extend the code however you like and if you want to share it, you can easily do so with a pull request * Mutual growth. The hope is with many using this library, test automation engineers can support each other and mutually create a library that makes common test cases easy * It itself is unit tested. You can have confidence of all the features shown and can add extra unit tests if you need more confidence * Built with and for CI in Gitlab. Designed to work within Docker containers * Will integrate with `sfdx` leveraging all of it's benefits * Supported by [Sentify](https://www.sentify.co/) who can provide [support and training](https://gitlab.com/leap-dojo/leap_salesforce/wikis/SupportModel) to help you get started and overcome challenges ## Table of Contents * [LeapSalesforce](#leapsalesforce) * [Table of Contents](#table-of-contents) * [Installation](#installation) * [Usage](#usage) * [Getting started](#getting-started) * [Understanding how things work](#understanding-how-things-work) * [Important files](#important-files) * [.leap_salesforce.yml](#leap_salesforceyml) * [salesforce_oauth2.yml](#salesforce_oauth2yml) * [config/general.rb](#configgeneralrb) * [Setup scripts](#setup-scripts) * [Changing environment](#changing-environment) * [Test Users](#test-users) * [Advanced](#advanced) * [Understanding how request is built](#understanding-how-request-is-built) * [CRUD of data](#crud-of-data) * [Creating entities](#creating-entities) * [Reading entities](#reading-entities) * [Retrieving entities](#retrieving-entities) * [Retrieving the value of a field](#retrieving-the-value-of-a-field) * [Updating entities](#updating-entities) * [Deleting entities](#deleting-entities) * [Other Examples](#other-examples) * [Structure](#structure) * [Docs](#docs) * [Development](#development) * [Contributing](#contributing) * [License](#license) * [Code of Conduct](#code-of-conduct) * [References](#references) > Note this documentation is a work in progress. Look at `spec` for code examples. ## Installation Add this line to your application's Gemfile: ```ruby gem 'leap_salesforce' ``` And then execute: $ bundle Or install it yourself as: $ gem install leap_salesforce ## Usage Note there is a `Leaps` alias that can be declared if you require `leap_salesforce/leaps` which makes commands shorter, meaning that `Leaps` can be used instead of `LeapSalesforce` ### Getting started After installing the gem, to get started in creating a fresh repository, the `leap_salesforce` executable can be used. It will ask for credentials and setup files that will locally store them. This assumes that a Salesforce OAuth app be set up to be used for the API which can be done by following [this wiki]('https://gitlab.com/leap-dojo/leap_salesforce/wikis/SetUpOAuthApp'). E.g ``` leap_salesforce init ``` Credentials are not stored in stored in source control. They can be setting through the following environment variables: * 'client_id' * 'client_secret' * 'password' > If these variables can be set in a `.env` file they will be loaded automatically > Security tokens can be set for each user (adding to password in OAuth like in [this article](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_login.htm)) > To do that set an environment variable as USER_KEY_token. E.g, so for a user with a key of :admin > the token would be set with the `admin_token` environment variable Tests can be run using the default `Rake` task. E.g., `rake` # Run all tests API Traffic logs can be seen in the `logs` folder. You can see an example of running through this [here](https://asciinema.org/a/259098). ### Understanding how things work This section details what the most important files are, how to define test users and how to create, read, update, and delete data. #### Important files To see how things fit together, look at the [structure](#structure) section below. ##### `.leap_salesforce.yml` This YAML file describes common configuration for the project. Following is a description of each key in this file: * `environment`: Specifies the default environment for this suite. This can be overwritten with the `LEAP_ENV` environment variable. * `lib_folder`: Can be set to change the default location (`lib/leap_salesforce`) of where all generated code is put and read from. * `soql_objects`: List of SOQL objects that the generator will create for and update * `sfdx`: Boolean for whether to use sfdx for authentication. Defaults to false ##### `salesforce_oauth2.yml` This file is used for using your own OAuth application. See next section for SFDX * client_id: OAuth2 client id / customer id obtained from your Test App * client_secret: OAuth2 client_secret / customer secret obtained from your Test App * password: Password expected to be generic across test users and the same as what's used on the UI to logon with This file is read and sets attributes of `LeapSalesforce` globally. These can also be set with the following format ```ruby LeapSalesforce.password = 'PASS' ``` The approach using these credentials follows [this tutorial](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/quickstart_oauth.htm) ##### `config/general.rb` This is where common code is stored for all the environments. This is where you would usually put your test users as described in the next section. For sfdx, set the `ENV['SF_USERNAME']` to the user to login to sfdx with and create a `server.key` for authentication. How sfdx will fully work with different user roles is still a work in progress. #### Setup scripts The `leap_salesforce init` runs 2 rake tasks on the initial suite after having created the basic files. These are: * `leaps:create_soql_objects` This reads from the list of soql objects defined in `.leap_salesforce.yml` and creates a class inheriting from `SoqlData` that maps to a backend object. A difference in name created from this script can be defined in that file with format `DesiredClassName:ActualClassName`. E.g., `Broker: Broker__c` will mean that a Ruby class called `Broker` will be created that will map to the `Broker__c` custom object. A separate file with the ending `_field_names` is also created from the metadata for each object. This creates accessors for each field in the table with names that map from a Ruby friendly name to the Salesforce backend name. A java class can be created using the following syntax in `.leap_salesforce.yml`. The default language will be Ruby ```yaml language: java lib_folder: main/java # Path to store auto generated Java classes soql_field_start_text: | package com.salesforce.test.pojo.soqlObjects; ``` These task can also be executed from the executable of this project with `leap_salesforce create_soql_objects --language=ruby` For generation of other languages, raise an issue * `leaps:create_enums` This task reads the soql objects defined in `.leap_salesforce.yml` and creates a `metadata/enum` folder that has picklist information for all the picklist fields of each object. These are designed to be readonly as they will be overridden when this command is run again. These are designed to be used as: 1. A reference when setting the value of a picklist. Rather than `contact.lead_source = 'Web'` one can use `contact.lead_source = Contact::LeadSource.web` which makes it clearer that a picklist value is used (not just free text) and also have a value that can be refactored if there is a change in the picklist. 2. A method of detecting changes in picklist values Sometimes due to older methods of deployment (or a disconnect between dev and testing team), a picklist value may change without the test team knowing about it (or a known change needs to be verified). Since the history of these values can be tracked when these files are committed to source control, a change can be detected when a test like the following is run ```ruby RSpec.describe 'Picklists' do LeapSalesforce.objects_to_verify.each do |data_class| SoqlEnum.values_for(data_class).each do |picklist| it "#{picklist} has not changed values" do expect(data_class.picklist_for(picklist.name)).to match_array picklist.values end end end end ``` #### Changing environment The `LEAP_ENV` environment variable can be used to change environment in non sfdx mode. The default is set through `.leap_salesforce.yml` environment key. In sfdx mode, setting env variable `SCRATCH_ORG` to `true` will tell leap salesforce to authenticate against a SCRATCH ORG. It will attempt to use either the `SCRATCH_ORG_ALIAS` environment variable. Alternatively, set the environment variables `SCRATCH_INSTANCE_URL` and `SCRATCH_ACCESS_TOKEN` from `sfdx:org:display` to enable tests to use that to authenticate to environment directly. #### Test Users Test users are defined using the `LeapSalesforce::Users` module. Following is an example of setting up a few test users: ```ruby module LeapSalesforce # Example where email address changes according to environment # Users can be added by passing an array or passing a LeapSalesforce::User object Users.add [:admin, 'admin@<%= LeapSalesforce.environment %>.email.com', description: 'System Admin User'] Users.add User.new :sales, 'test.sales@test<%= LeapSalesforce.environment %>.com' end ``` The first user defined will be the default user. Following users can be set by using the `api_user` attribute. ```ruby # Using key to specify user LeapSalesforce.api_user = LeapSalesforce::Users.where(key: :sales) # Using username that has a partial match with a Regex LeapSalesforce.api_user = LeapSalesforce::Users.where username: /admin/ # Using description that has a partial match with a Regex. This might be helpful if you're setting users from # a Cucumber step definition where readability is important LeapSalesforce.api_user = LeapSalesforce::Users.where description: /System Admin/ ``` #### Advanced Topics in here for understanding how things work behind the scenes and are not necessary for simple use of this gem. ##### Understanding how request is built ###### Creating entities When creating entities, the convention is that the accessors defined by the `soql_element` that are auto-generated by the `leaps:create_soql_objects` rake task are used. These methods internally call the `[]=` method to set values that will be sent in the POST request. This method can be used directly with the backend name. E.g, `contact[:FieldName] = 'Value'` This method in turn sets a value of the `@override_parameters[:body]` variable within the entity. The value of request body can be interrogated with `entity.request_parameters.body`. ##### Logging By default, API traffic will be logged in a log file in a `logs` folder. The gem `soaspec` is used to log this traffic. Following is an example of changing some of the default logging. ```ruby # Turn this true if you need debug authentication Soaspec::OAuth2.debug_oauth = true # Turn this to true if you want to see API traffic on the terminal Soaspec::SpecLogger.output_to_terminal = true ``` See more configuration parameters in the [Soaspec repo](https://gitlab.com/samuel-garratt/soaspec#logging) ### CRUD of data To work data in Salesforce, an object inheriting from the `SoqlData` class is always used. The idea is that an object in Ruby code maps to the object in Salesforce and requests and updates to this object are reflected in Salesforce. When the initialisation script is run, it creates such classes in a folder called `soql_data`. Following a simple example of a class representing the 'ContentDocument' object in Salesforce. It also requires a generated file that specifies accessors to set and retrieve information about the object. ```ruby require_relative 'document_field_names' # An Document object mapping to a SOQL ContentDocument class Document < SoqlData include Document::Fields soql_object 'ContentDocument' end ``` For all interactions with Salesforce the API traffic logs are recorded in a log created in the `logs` folder of the suite. #### Creating entities There are several ways entities can be created. By instantiating the object with the `new` method a new object will be created in memory but in Salesforce. Only when the `save!` method is called will an object be created. For example ```ruby @contact = Contact.new # Create an object in memory @contact.last_name = 'Test Person' # Set the last name field of that object to 'Test Person' @contact.save! # Calls Salesforce API to create a new object with the fields set in the object ``` The `log` for this call will look like the following: (Note that explanations are in ALL CAPS and line numbers have been added) ```verilog 1. Leaps, [13:39:14] : Example Factory for 'Contact' 2. Leaps, [13:39:14] : request body: {"LastName":"Gleichner"} 3. Leaps, [13:39:14] : RestClient.post "https://SALESFORCE_INSTANCE_URL/services/data/v45.0/sobjects/Contact", "{\"FirstName\":\"Lewis\"}", HEADERS INFO 4. Leaps, [13:39:16] : # => 201 Created | application/json 71 bytes 5. Leaps, [13:39:16] : response: 6. headers: {:date=>"Thu, 01 Aug 2019 01:39:15 GMT", OTHER_HEADER_INFO} 7. body: {"id":"0032v00002qU3hvAAC","success":true,"errors":[]} ``` Following is a step by step explanation of each log line: 1. Brief description of what's being done. Creating a Contact. 2. Parameters used in HTTP request. This will be the fields set on the object created 3. The actual REST Post made to the Salesforce API showing the URL, payload, headers, etc 4. A brief summary of the response. Object has been created successfully 5. A more in depth description of the response follows in next two lines 6. Headers of the response 7. Body of the response Representation of the details for created an entity can be handled much neater by `FactoryBot`. If we had the following `factory` for Contact ```ruby FactoryBot.define do factory :contact do last_name { 'Test Person' } end end ``` then we could perform the same creation of a contact with simply ```ruby @contact = FactoryBot.create(:contact) ``` FactoryBot has traits, associations, after blocks for helping with creating objects with fast number of relationships. See FactoryBot's getting started for more information and have a look at the examples in the `spec` folder. To create an object using a factory, the `create` method can also be used on the object itself. For example: ```ruby @contact = Contact.create ``` ##### Composite creation When creating bulk data (e.g, data with related fields), it's faster to use the composite API. See `spec/integration/composite_spec.rb` for an example of how to do this. This will be built into this gem more in the future (so that composite requests are made automatically). Following is an example of the time difference for just putting 2 requests into one request. Create Account: 0.96s Create Contact linked to this account: 0.75s Total: 1.7s Create Contact and account using Composite: 1.24s #### Reading entities ##### Retrieving entities To retrieve an entity, the `find` method can be called on the class for the object required. For example to obtain an object representing a contact with a last name of 'Test Person' we could do: ```ruby @contact = Contact.find last_name: 'Test Person' ``` This uses the ruby friendly method defined for `Contact` and shown in `Contact::Fields` to extract the Salesforce field name `LastName`. Note, the name designated is derived from the `label` name which is assumed to be more user friendly than the backend name. See `spec/unit/ext/string_spec.rb` for examples of how different behaviours are handled. The backend name can also be used directly within a `find` so the following could also be done: ```ruby @contact = Contact.find LastName: 'Test Person' ``` The values used in these requests are validated against Metadata before the request is made so an error will be received if a field name is used that does not exist on the object. Any number of parameters can be passed to the find method to narrow the search. For example: ```ruby @contact = Contact.find last_name: 'Test Person', first_name: 'Number 1' ``` When a date is passed as the value it will be automatically formatted so that it can be used in the backend SOQL query. So to get a contact created less than 5 days ago one can use: ```ruby @contact = Contact.find created_date: "<#{5.days.ago}" ``` To use the `LIKE` operator for partial matches put a '`' at the start of the string. Following is an example of finding a contact that has the string 'Test' anywhere in their first name. As one can see the '%' symbols are used as a wild card to indicate any value. ```ruby @contact = Contact.find(first_name: '~%Test%') ``` Note for non unique criterion, the value returned will be the most recent one. ##### Retrieving the value of a field Getters (methods that retrieve something about an object) are created for each field name on an object. So to get the first name of the contact, the `first_name` method is simply called on it. ```ruby @contact = Contact.find first_name: 'Test Person' @contact.first_name # => 'Test Person' ``` The backend name can also be used in the `[]` method to retrieve a value. ```ruby @contact['FirstName'] # => 'Test Person' ``` There are 2 special methods related to verifying that a response is successful or not. * `success` - returns `true` or `false` indicating whether the previous action performed worked as expected * `error_message` - returns a string with the error message returned from Salesforce. For example, say you want to test that a `LineItem` cannot be deleted. You can verify it and it's error message with: ```ruby item = LineItem.find status: 'New' expect(item.delete.error_message).to eq 'Deleting of line item is not allowed.' ``` #### Updating entities The same field name used above is used as a setter to update an individual field. For example, to change the first name of a contact from Test1 to Test2: ```ruby @contact = Contact.find first_name: 'Test1' @contact.first_name = 'Test2' ``` To update multiple fields at once, use the `update` method: ```ruby @case.update status: Case::Status.escalated, case_reason: 'Feedback' ``` This does not fail if the update is not successful. To fail in this situation, the `success_update` method can be used: ```ruby @case.success_update status: Case::Status.escalated, case_reason: 'Feedback' ``` If the main action of the test is this update, it is recommended that success be verified explicitly with: ```ruby @case.update status: Case::Status.escalated, case_reason: 'Feedback' expect(@case).to be_successful ``` The reason for this is to account for error scenarios where you want to update a value and expect and error message. For example: ```ruby item = LineItem.find(status: 'New') update = item.update owner_id: User.find(Name: '~%Confidential%').id expect(update.error_message).to eq 'Cannot change owner to confidential user' ``` #### Deleting entities Once an entity is obtained through `find` or `create`, it can be deleted simply with the `delete`. If you want an exception to be raised if the delete fails, pass `must_pass: true` to the delete method. For example: ```ruby @contact = Contact.find first_name 'No longer needed' @contact.delete must_pass: true ``` An entity can also be deleted with merely it's id. For example: ```ruby Contact.delete '0032v00002rgv2pAAA', must_pass: true ``` ### Other Examples See [spec/integration](spec/integration) folder for examples of tests. #### Delete old contacts ```ruby # Deleting old contacts objects = Contact.each_id_with created_date: "<#{5.days.ago}" puts objects.count # Log how many are being deleted objects.each do |id| puts "Deleting #{id}" Contact.delete id end ``` ## Structure Following is the general structure of test automation suite that uses this approach. Details may vary depending on the test framework used and other preferences. . ├── config # Code for the configuration of the automation suite │ ├── general.rb # Code loaded for common (non confidential code) setup across environments │ ├── credentials # Setting of secret properties like passwords │ │ └── salesforce_oauth.yml # Credentials for connecting to Salesforce via OAuth2. │ └── environments # Contains ruby files loaded specific to environment following `ENVIRONMENT_NAME.rb` ├── lib # Common library code │ └── leap_salesforce # Code generated by or specific to leap_salesforce (this folder can be configured in .leap_salesforce.yml) │ ├── factories # FactoryBot definitions, describing how to mass produce objects │ ├── metadata # Code generated and updated automatically from metadata │ │ └── enum # Picklist enumeration objects are stored here │ └── soql_data # Objects for handling each object in the backend specified in '.leap_salesforce.yml' │ ├── {object_name} # Class mapping to a Soql object │ └── {object_name}_field_names # Field name accessors auto created/updated based no metadata in Salesforce ├── logs # Contains API traffic logs for transactions against Salesforce ├── spec # Where RSpec automated tests are stored ├── .leap_salesforce.yml # Where common configuration is stored regarding your project. This complements and is read before what's in 'config' ├── Gemfile # Where required ruby gems/libraries are specified ├── Gemfile.lock # Generated file specified details of versions installed by `Gemfile` └── Rakefile # Where common `Rake` tasks are specified. LeapSalesforce specific tasks are required from here ## Docs Technical docs [here](https://www.rubydoc.info/gems/leap_salesforce) ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitLab at https://gitlab.com/leap-dojo/leap_salesforce. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Code of Conduct Everyone interacting in the LeapSalesforce project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/leap-dojo/leap_salesforce/blob/master/CODE_OF_CONDUCT.md). ## References * Presentation on this library [here](https://gitpitch.com/leap-dojo/leap_salesforce?grs=gitlab) * Example of this library within a CI/CD pipeline [here](https://gitlab.com/iqa_public/labs/salesforce_cicd_demo) * Using `leap_salesforce` to download event log files [here](https://gitlab.com/samuel-garratt/leap_salesforce_event_log_files) * Video walking through setting up automation using this [here](https://youtu.be/Xvj0mAnDKfA)