Language agnostic Salesforce Apex unit test

I was doing some work with packaging on Salesforce and used the dreamhouse-lwc repo as a foundation. When I was building package versions the Apex unit tests were failing as the SOQL queries is using WITH SECURITY_ENFORCED and the user running the queries did not have the right access. The solution was to update the unit test to create a user and assign the dreamhouse Permission Set but to create a user you need to set a Profile. Which one to pick? Easy – use the “Standard User” Profile which is easily accessible by SOQL:

SELECT Id FROM Profile WHERE Name='Standard User' LIMIT 1

This code failed however as the Profile couldn’t be found. It turned out to be because the scratch org created was in Danish so the Profile is called “Standard Bruger” instead. This could be solved by setting the language of the scratch org by using the language key in config/project-scratch-def.json but the repo maintainers didn’t want that. A more flexible and still language agnostic way was to query more intelligently for the Profile. The below SOQL query achieves the same result as above but without setting the org language.

SELECT Name, Id
FROM Profile
WHERE UserType = 'Standard' AND PermissionsPrivacyDataAccess = false AND PermissionsSubmitMacrosAllowed = true AND PermissionsMassInlineEdit = true LIMIT 1

Turning on trace debugging with the Salesforce CLI

When using the Salesforce CLI as the primary way to interact with a scratch org turning on Apex trace debugging can be a tiresome-I-have-to-click-into-the-org situation. Usually you are not able to use force:apex:log:list and force:apex:log:get commands to work with Apex logs without first opening the Web Developer in the org or setting up trace logging.

But it turns out there is a better way using a simple script.

The below script does the trick for you can easily be added to your process for creating new scratch orgs. The script sets up some timestamps and then queries for the scratch org user userId. Then we get the Id of the trace log configuration for the user and then updates the record to enable trace logging for 24 hours.

NOW=`date -u +"%Y-%m-%dT%H:%M:%SZ"`
EXP=`date -v+24H -u +"%Y-%m-%dT%H:%M:%SZ"`

USERID=`sfdx force:data:soql:query -q "select id,name from user where name='User User'" --json | jq ".result.records[0].Id" -r`

TRACEID=`sfdx force:data:soql:query --query "SELECT Id, DebugLevel.DeveloperName, ExpirationDate, TracedEntityId FROM Traceflag WHERE TracedEntityId IN (SELECT ID from USER WHERE ID = '${USERID}')" --usetoolingapi --json | jq ".result.records[0].Id" -r`

sfdx force:data:record:update --sobjecttype TraceFlag --sobjectid $TRACEID -v "StartDate=$NOW ExpirationDate=$EXP" --usetoolingapi --json --loglevel fatal

The above script uses jq (https://stedolan.github.io/jq/) for JSON parsing and works on Mac (date command switches is slightly different on Linux) so YMMV.

Individual Object enablement in Salesforce Scratch orgs – now available!

I’m so happy that one of my scratch org pet peeves finally made it into the product for the Spring 19 release. It has not been possible to enable the Individual Object when creating new scratch orgs and IMO this has been lacking and a cause for many issues, discussions and workarounds in customer projects. But all this is a thing of the past now! Starting Spring 19 you may add the below orgPreferenceSetting to your scratch org definition file:

"settings": {
    "orgPreferenceSettings": {
      "consentManagementEnabled": true
    }
 }

Now the setting hasn’t made it into the documentation as of this writing but I know that it’s in the works. No go and create scratch orgs with the Individual Object enabled!

Further reading:

Calling a Swagger service from Apex using openapi-generator

For a demo I needed to make calls to the Petstore API using a Swagger / OpenAPI definition from Apex. Unfortunately the Apex support that External Services in Salesforce provides is only callable from Flows so I needed another approach. Using openapi-generator and installing via Homebrew it went relatively smoothly anyway.

It’s always nice to stand on the shoulder of giants so the examples provided by Rene Winkelmeyer (Connecting to Swagger-backed APIs with Clicks or Code ) came in handy. Unfortunately didn’t work as the prefix for the generated classes was different plus the petId was was 1 and not 100. Anyway again easy to fix.

Below are my steps to get working using Homebrew and Salesforce DX.

$ brew install openapi-generator
$ openapi-generator generate \
-i https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml \
-g apex \
-o /tmp/petstore_swagger/
$ cd /tmp/petstore_swagger
$ sfdx force:org:create \
-v my_devhub \
-f config/project-scratch-def.json -a petstore_swagger
$ sfdx force:source:push -u petstore_swagger
$ sfdx force:apex:execute -u petstore_swagger
>> Start typing Apex code. Press the Enter key after each line,
>> then press CTRL+D when finished.
OASClient client = new OASClient();
OASPetApi api = new OASPetApi(client);
Map<String,Object> params = new Map<String, Object>();
Long petId = Long.valueOf('1');
params.put('petId', petId);
OASPet pet = api.getPetById(params);
System.debug(pet);
<Ctrl-D>

Salesforce DX error output will go to stdout starting with v45

Now this is very good news for us that are doing scripting with Salesforce DX. Currently the Salesforce DX CLI (sfdx) will send its output to stdout (standard out) when the command succeeds and to stderr (standard error) if the command fails. Why would a command fail? Well if you try and describe an org with a wrong or non-existing alias that’s one reason.

Now this is really the expected behaviour for a CLI but when doing scripting using the –json flag to always have the response returned as JSON it just makes it more complicated. For one the response already have a “status”-flag to indicate whether the invocation was successful or not. Secondly having to deal with stdout and stderr is not difficult but leads to boilerplate scripting code as what you really want to do is capture the response and check that “status”-flag in the JSON. Also it makes it harder to adopt for people new to CLI scripting and the way it’s done across platforms differ.

Now to the good news.

As of v44, there is an environment variable to have all JSON output go to stdout. Set SFDX_JSON_TO_STDOUT=true to get this new behaviour. This will become the default behaviour in v45. Now it’s not listed on the environment variables list for Salesforce DX yet but I trust it will be soon.

Yay!! 🙏

 

jq and multi-field output to CSV

jq is some of the most underrated tools out there I think. It’s a command line JSON parser that makes it super easy to work with JSON on the command line and in turn makes developing small SalesforceDX tools a breeze. Today I needed to generate a CSV file of all fields from different objects for the integration team that doesn’t have access to Salesforce. Doing the describe is easy using the Salesforce REST API but when using jq different are usually on different lines like below (-r is a nifty switch for getting raw, unquoted strings).

$ sfdx force:schema:sobject:describe -s Account -u myorg --json | jq -r ".result.fields[] | .label, .name"
Age
Age__pc
PO Box
PO_Box__pc
Postal Code Before City
Postal_Code_Before_City__pc
Street No Before Street
Street_No_Before_Street__pc
Archived State
Archived_State__pc

The output is almost what I wanted but really wanted not  to have to edit the file manually to build the output. Some quick googling and it appears that jq supports both CSV and tabular output from arrays. So fixing the issue was as simple as follows:

$ sfdx force:schema:sobject:describe -s Account -u myorg --json | jq -r ".result.fields[] | [.label, .name] | @csv"
"Age","Age__pc"
"PO Box","PO_Box__pc"
"Postal Code Before City","Postal_Code_Before_City__pc"
"Street No Before Street","Street_No_Before_Street__pc"
"Archived State","Archived_State__pc"

This is so cool… Love it!!

How to add missing SalesforceDX org alias

If you ever added a non-scratch org in SalesforceDX but forgot to add an alias using –alias / -a or simply want to change an alias there is a way easier way when readding all orgs. Simply edit the alias.json file as below.

  1. Locate your SalesforceDX settings directory (on Mac that is in ~/.sfdx and on Windows it seems to be %USERPROFILE%\.sfdx)
  2. Edit the alias.json file (please note that it uses Unix style endings so use an appropriate editor)
  3. Add missing alias mapping or correct incorrect mappings. The file simply maps an alias to the org username shown in “sfdx force:org:list”. Below is an example file.
{
 "orgs": {
   "example.qa": "mheisterberg@example.com.qa",
   "example.uat": "mheisterberg@example.com.uat"
 }
}

Bash one-liner for Apex test coverage percentage using SalesforceDX

Update 3 May 2018: There are issues with the percentages reported by SalesforceDX plus it doesn’t report coverage on classes with 0% coverage which will shrew the results. The approach outlined above can be used as an indication but cannot as of today be used as a measure for code coverage when it comes to production deployments. As an example I’ve had the above snippet report a coverage of 88% where as a production deploy reported 63% coverage. We – Salesforce – are aware of the issue and are working to resolve it. Stay tuned!

Note to self – quick note on how to run all tests in a connected org (as identified by the -u argument) and use jq and awk to grab the overall test coverage percentage.

$ sfdx force:apex:test:run -u mheisterberg@example.com.appdev -c -w 2 -r json | jq -r ".result.coverage.coverage[].coveredPercent" | awk '{s+=$1;c++} END {print s/c}'
> 88.1108

YMMV!

 

Using SalesforceDX to perform Bulk API operations

As noted the other day (Using SalesforceDX to automate getting Apex class test coverage percentages) SalesforceDX is great for many things but one of the ways is automate some operations that are time consuming or just takes a lot of manual work each time. One of these things are Bulk API operations which in of by themselves are not hard but there is no UI for them besides the DataLoader and no console API when using the DataLoader when not on Windows.

The customer I’m working for currently has a monster data load to perform and one of the things I’ve done is writing  script to split the data into data sets. One set per country – 91 sets all in all. All sets consists of 3 files to support the data load. One file for Accounts and two additional files for custom objects that needs to be loaded as well. All in all it’s a lot of clicking in the DataLoader and it doesn’t really scale for testing. That’s a lot of clicking in DataLoader when testing.

But I’m lucky as SalesforceDX receives new functionality all the time and at some point some data Bulk API features had snuck by me so I was pleasantly surprised to discover force:data:bulk:upsert and force:data:bulk:delete today. They we just what I needed. SalesforceDX to the rescue yet again…

So today I grabbed by IDE by the horns (#vscode in my case) and wrote some wrappers around the Bulk API capabilities of SalesforceDX. The fact that all SalesforceDX commands takes an optional –json argument makes it easy to script and parse responses. This combined with select-shell from npm I now have a nice CLI interface to doing Bulk data loads. The script looks as the available data sets and asks me what country to load data for and then what export timestamp to process (the data sets may exists in multiple versions). Then it goes and does its thing UPSERTing all 3 times in turn and reports status. So nice. The Bulk API is asynchronous so  the script also handles polling for job status and only proceeds once the job has completed successfully.

$ ./upsert mheisterberg@example.com.appdev
SFDX - Org for mheisterberg@example.com.appdev is connected...

Select country code:
 ae
 au
 ca
 cn
 es
 fr
 hk
 hu
 co
 ▸ it
 jp
 kr
 my
 pt
 sg
 th
 tr
 tw
 us

Select timestamp:
 2018-04-16T07:25:37Z
 2018-04-16T08:31:28Z
 ▸ 2018-04-16T08:34:14Z

Will process following data
Country : it
Timestamp: 2018-04-16T08:34:14Z
UPSERT for Account data...
Issued UPSERT bulk request to object (Account) - id 7516E000002DckQQAS, jobId 7506E000002QQ3zQAG - state: Queued
SFDX - asking for bulk status for id 7516E000002DckQQAS, jobId 7506E000002QQ3zQAG
SFDX - received bulk status for id 7516E000002DckQQAS, jobId 7506E000002QQ3zQAG - state: Completed
Issued UPSERT bulk request to object (MarketRelation__c) - id 7516E000002DckkQAC, jobId 7506E000002QQ4JQAW - state: Queued
SFDX - asking for bulk status for id 7516E000002DckkQAC, jobId 7506E000002QQ4JQAW
SFDX - received bulk status for id 7516E000002DckkQAC, jobId 7506E000002QQ4JQAW - state: Completed
Issued UPSERT bulk request to object (Consent__c) - id 7516E000002DckuQAC, jobId 7506E000002QQ4OQAW - state: Queued
SFDX - asking for bulk status for id 7516E000002DckuQAC, jobId 7506E000002QQ4OQAW
SFDX - received bulk status for id 7516E000002DckuQAC, jobId 7506E000002QQ4OQAW - state: Completed
Finished upsert of data

Once I’m done testing a particular data set I can use the –delete-accounts flag to my script to delete data using the Bulk API as well. Here I actually combined force:data:soql:query and force:data:bulk:delete to first retrieve the ID’s of the records I need to delete and then kick off the required Bulk API delete requests. Again easy peasy. And repeatable…

$ ./upsert mheisterberg@example.com.appdev --delete-accounts
SFDX - Org for mheisterberg@example.com.appdev is connected...

Are you sure?
 ▸ No
 Yes

Received 32463 records
Issued DELETE bulk request to object (Account) - id 7516E000002DckLQAS, jobId 7506E000002QQ3uQAG - state: Queued
SFDX - asking for bulk status for id 7516E000002DckLQAS, jobId 7506E000002QQ3uQAG
SFDX - received bulk status for id 7516E000002DckLQAS, jobId 7506E000002QQ3uQAG - state: InProgress
SFDX - asking for bulk status for id 7516E000002DckLQAS, jobId 7506E000002QQ3uQAG
SFDX - received bulk status for id 7516E000002DckLQAS, jobId 7506E000002QQ3uQAG - state: Completed
Performed delete...

Only issue I had here really was that node.js has a maximum buffer size of 200kb to stdin so I could not simply read the stdin response from the SOQL query as it may be pretty big. Instead I pipe to a tmp-file and read that back in and parse as JSON. Not ideal but it gets the job done.

The code of the script itself is for the customers eyes only but the source for the helpers is available as sfdx-bulk-helper on Github and sfdx-bulk-helper on npm.

YMMV!