# 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. In the future it will also planned to be used to create page objects based on metadata to support UI testing. [![Build Status](https://gitlab.com/leap-dojo/leap_salesforce/badges/master/build.svg)](https://gitlab.com/leap-dojo/leap_salesforce/pipelines) ## 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) * [Test Users](#test-users) * [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 ### 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' 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 ##### `salesforce_oauth2.yml` * 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' ``` ##### `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. #### 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/ ``` ### 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 ``` #### 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_oauth2.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 │ ├── 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' ├── 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 See the presentation on this [here](https://gitpitch.com/leap-dojo/leap_salesforce?grs=gitlab)