Skip to content

Testing A Connector Locally

If you want to validate the code of a connector locally, you need to have a data object and at least one Cache Writer or one Action Processor implemented and know the API you are building the connector for.

At this point

  • You understand what a connector is.
  • You have implemented the API Client and related files.
  • You have a data object and at least one Cache Writer or Action Processor implemented

To test the connector locally, the CLI and connector project offers some useful functionality. You can verify if the connector works by:

  1. Initializing a test-settings.json file
  2. Configuring test-settings.json in your root "Connector" folder
  3. Configuring launchSettings.json in the "Properties" folder
  4. Using your development environment or command line to run

Instructions

Run Test Init Command

Always build your project before running xchange test init to keep the file up to date.

Note

Starting from version 1.3.6 of the CLI doing a build first is no longer required. xchange test init will trigger a build automatically

This creates a file named test-settings.json and populates it scaffolding based on your current connecter setup. Like most commands, make sure to execute it in the "Connector" directory where Connector.csproj is located.

Command

xchange test init

Warning

Must execute the command in the directory where the csproj file of the connector is, otherwise you will get an error message like the following:

xchange test init
Error Time: 11:42:29.299 Could not find a CSPROJ file in the directory: /Users/builder/Documents/svc/trimble/connector-product

Expected Results on Success

xchange test init
Information Time: 11:44:41.110 /Users/builder/Documents/svc/trimble/connector-product/Connector/test-settings.json could not be found. Creating a new file.
Information Time: 11:44:41.191 Test data generated in /Users/builder/Documents/svc/trimble/connector-product/Connector/test-settings.json

Enter Configuration In the Test Settings file

Test-settings.json contains several different sections which will need to be configured, including connectorRegistration, activeConnection, connections, serviceConfiguration, and data. By default it is listed in gitignore and you will need to enter credentials here to test your api such as username, password, bearer token, etc. as appropriate for your authorization setup.

{
  "testSettings": {
    "connectorRegistration": {  }, // api configuration
    "activeConnection": "apiKeyAuth", // definition key of connection to use
    "connections": [ // predefined connections to test against
        {
            "definitionKey": "apiKeyAuth",
            "type": "Standard",
            "configuration": { // Update these values with your connection values / credentials.
            "apiKey": "*YOUR API KEY VALUE*",
            "connectionEnvironment": 1
            }
        }
    ],
    "app-1": { // module to test
      "serviceConfiguration": {  } // configuration of the module
      "data": {  } // test data objects
    }
  }
}

connectorRegistrations

The connectorRegistration section coordinates with what you have under the Connector/ConnectorRegistrationConfig.cs. On the App Xchange platform these fields would be completed by the user. It may remain blank, as most connection details are now handled via structured Connections, but may be used for workspace level filtering. Enter test data here as needed.

{
  "connectorRegistration": {
    "companyId": 123,
    "accountId": "myTestAccountIdHere"
  },
}

activeConnection

Define the connection you want to use for your test. Enter the definitionKey of the connection you want to test with.

{
   "activeConnection": "apiKeyAuth",
}

connections

An array of credentials that you want to test against. Only one will be used per test run.

    {
      "testSettings": {
        "connectorRegistration": {  },
        "activeConnection": "apiKeyAuth", // definition key of the connection to test.
        "connections": [ // Your created connections.
          {
            "definitionKey": "apiKeyAuth",
            "type": "Standard",
            "configuration": { // Update these values with your connection values / credentials.
            "apiKey": "*YOUR API KEY VALUE*",
            "connectionEnvironment": 1
            }
          }
        ],
        ...
      }
    }

Module ID section (app-1 default example)

Contains the serviceConfiguration and data keys for each module to test. The serviceConfiguration section contains information about what Action Processors and Cache Writers exist for the connector, and which ones to test. Set "UploadObject" to "true" to run and "false" to ignore/skip. The data section contains information on the data objects broken down by event and type.

{
 "app-1": {
  "serviceConfiguration": {
     "actionProcessor": {
        "AddHealthCheckConfig": {
           "ProcessQueuedEvent": true,
            "TestAPIFailure": false,
             "TestCacheSyncFailure": false
          },
         },
      "cacheWriter": {
         "HealthCheckConfig": {
            "UploadObject": true,
            "ValidateCacheInsert": false,
            "ValidateCacheUpdate": false,
            "ValidateCacheDelete": false
          }
        }
      }
  },
  "data": {
     "health-check": {
        "add": [
           {
            "Timestamp": "2024-06-25T22:50:19.9781118Z"
           }
          ]
      }
  }
}
Full Example of a test-settings.json
  1. Non connection configuration needed for interacting with the external system API.
  2. Which connection to use
  3. List of available connections
  4. Flag to enable the CreateEmployees action when the Action Processor Local Development profile runs.
  5. Flag to enable the cache writer of the Employees data object when the Cache Writer Local Development profile runs.
  6. All the CreateEmployeesActionInput properties that are used to test the CreateEmployees action.
  7. All the UpdateEmployeesActionInput properties that are used to test the UpdateEmployees action.
{
  "testSettings": {
    "connectorRegistration": {
      "apiDetails": { // (1)
        "cuisine": "",
        "type": "",
        "diet": "",
      }
    },
  "activeConnection": "basicAuth", // (2)
  "connections": [ // (3)
    {
      "definitionKey": "apiKeyAuth",
      "configuration": {
        "apiKey": "",
        "connectionEnvironment": 1
      }
    },
    {
      "definitionKey": "basicAuth",
      "configuration": {
        "username": "",
        "password": "",
        "connectionEnvironment": 1
      }
    }
  ],
    "app-1": {
      "serviceConfiguration": {
        "actionProcessor": {
          "AddHealthCheckConfig": {
            "ProcessQueuedEvent": true,
            "TestAPIFailure": false,
            "TestCacheSyncFailure": false
          },
          "CreateEmployeesConfig": {
            "ProcessQueuedEvent": true // (4)
          },
          "UpdateEmployeesConfig": {
            "ProcessQueuedEvent": true
          },
          "DeleteEmployeesConfig": {
            "ProcessQueuedEvent": true
          }
        },
        "cacheWriter": {
          "HealthCheckConfig": {
            "UploadObject": true,
            "ValidateCacheInsert": false,
            "ValidateCacheUpdate": false,
            "ValidateCacheDelete": false
          },
          "EmployeesConfig": {
            "UploadObject": true // (5)
          }
        }
      },
      "data": {
        "health-check": {
          "add": [
            {
              "Timestamp": "2024-06-25T22:50:19.9781118Z"
            }
          ]
        },
        "employees": {
          "create": [ // (6)
            {
              "firstName": "",
              "lastName": "",
              "phone": "",
              "email": "",
              "address": "",
              "city": "",
              "state": "",
              "zip": ""
            }
          ],
          "update": [ // (7)
            {
              "employeeId": 0,
              "firstName": "",
              "lastName": "",
              "phone": "",
              "email": "",
              "address": "",
              "city": "",
              "state": "",
              "zip": ""
            }
          ],
          "delete": [
            {
              "employeeId": 0
            }
          ]
        }
      }
    }
  }
}

Run Test Locally

Now that you've configured test-settings.jon, locate Connector/Properties/launchSettings.json. This file contains information about the launch profile, such as the application URL and the environment variables.

What does the launchSettings file contain and what is its purpose:

The launchSettings.json is a configuration file used in C# projects. It defines various settings related to the application's launch, such as environment variables, command-line arguments, and debugging settings. This file is located within the "Properties" folder of your project and is used by the development environment to determine how the application should run. For example, you can read more about how to use it in visual studio code here: https://code.visualstudio.com/docs/csharp/debugger-settings#_launchsettingsjson-support.

In the launchSettings file locate the three sections.

Action Processor Local Development 
Cache Writer Local Development
Test Connection Local Development
These are the sections we care about.

Testing Cache Writer

You will likely only need to change the module-id, if anything. This will run the service configuration you set up above for the cache writer, testing a cache write of objects for which uploadObject was set to true.

Command

dotnet run --launch-profile "Cache Writer Local Development"

This or any profile can be run via the IDE of your choosing as well. Feel free to use which one you are comfortable with.

Example Expected Results on Success

$ dotnet run --launch-profile "Cache Writer Local Development"
Local development is currently configured. You'll need to use the Xchange.Connector.SDK.Test.Local library
<6>ESR.Hosting.RunService[0] ESR (1.0.6.0) Host started in 0.646475 seconds
<6>ESR.Hosting.RunService[0] Running job 60ab563e-06be-491b-ac91-78b0d668288e for Subscriber Id 0
Time to Retrieve Service Run Message and Signal Started: 966
Time to initialize context: 68
Time to validate context: 94
[12:17:28 INF] Writing <connector-key/app/1/employees> (Asynchronous) data objects to cache
[12:17:29 INF] Change detection system determined that there were 10 new records, 0 updated records, 0 unchanged records, and 0 deleted records for object of type <connector-key/app/1/employees>.
[12:17:29 INF] App Network sync summary for connector-key/app/1/employees
Inserts: 10 successful, 0 no change, 0 failed
Updates: 0 successful, 0 no change, 0 failed
Deletes: 0 successful, 0 no change, 0 failed
Time to Finish Service: 752
<6>ESR.Hosting.RunService[0] Service runner shutting down…

Note

When you run a cache writer or action processor a directory named ‘databases’ gets created in the process's current working directory, such as the bin directory. These databases are an emulation of the Xchange platform. More on this in the section Emulation Databases

Testing Action Processor

  1. Locate the section Action Processor Local Development
  2. Under the field/property commandLineArgs
  3. The default template will appear as "commandLineArgs": "--isLocal true --moduleId app-1 --serviceType ActionProcessor --dataObjectUrl health-check --actionUrl add", (You may not have a health-check object unless your module was called with the optional --add-health-checks). Fill out the --dataObjectUrl XXXX --actionUrl XXXX part of the command line for data object and action - note it is case sensitive based on the CLI command.

Example

--dataObjectUrl employees --actionUrl create

Command

dotnet run --launch-profile "Action Processor Local Development"

Expected Results on Success

Local development is currently configured. You'll need to use the Xchange.Connector.SDK.Test.Local library
<6>ESR.Hosting.RunService[0] ESR (1.0.6.0) Host started in 0.42326 seconds
<6>ESR.Hosting.RunService[0] Running job 35b583fe-ffb8-4090-bfd9-98a335313a19 for Subscriber Id 0
Time to Retrieve Service Run Message and Signal Started: 928
Time to initialize context: 76
Time to validate context: 89
[12:24:53 INF] Starting action processor for any queued actions
[12:24:54 INF] Action with ID ffd85a7f-335a-4c38-8cfb-d182c974e0c2 closed as Successful 
[12:24:54 INF] Processing actions complete. 1 were handled. 0 queued actions were skipped
Time to Finish Service: 424
<6>ESR.Hosting.RunService[0] Service runner shutting down...

Expected Error If Data Object or Action Command Line Argument Are Wrong

dotnet run --launch-profile "Action Processor Local Development"
Local development is currently configured. You'll need to use the Xchange.Connector.SDK.Test.Local library
<6>ESR.Hosting.RunService[0] ESR (1.0.6.0) Host started in 0.503598 seconds
<6>ESR.Hosting.RunService[0] Running job fc746eda-9a10-49a4-b3ac-9e565be8ecf3 for Subscriber Id 0
Time to Retrieve Service Run Message and Signal Started: 1016
Time to initialize context: 76
Time to validate context: 84
[12:24:38 INF] Starting action processor for any queued actions
[12:24:38 ERR] An error occurred during action processing
System.Exception: Unable to resolve an IAction<> type for the module 'app-1' and the DataObject 'employees' and its action 'Create'
  at Xchange.Connector.SDK.Test.Local.TestDataSource.GetIActionInterface()
  at Xchange.Connector.SDK.Test.Local.TestDataSource.GetTestQueuedData()
  at Xchange.Connector.SDK.Test.Local.Client.LocalAppNetworkClient.GetQueuedActions(String urlPart, String actionPath, CancellationToken cancellationToken)
  at ESR.Hosting.Action.ActionHandlerService.HandleActionsInActionQueueAsync()
<3>ESR.Hosting.RunService[0] Exception while running service System.Exception: Unable to resolve an IAction<> type for the module 'app-1' and the DataObject 'employees' and its action 'Create'    at Xchange.Connector.SDK.Test.Local.TestDataSource.GetIActionInterface()    at Xchange.Connector.SDK.Test.Local.TestDataSource.GetTestQueuedData()    at Xchange.Connector.SDK.Test.Local.Client.LocalAppNetworkClient.GetQueuedActions(String urlPart, String actionPath, CancellationToken cancellationToken)    at ESR.Hosting.Action.ActionHandlerService.HandleActionsInActionQueueAsync()    at Xchange.Connector.SDK.Hosting.ServiceRunner.RunServiceAsync(IConnectorServiceDefinition serviceDefinition, CancellationToken cancellationToken)    at Xchange.Connector.SDK.Hosting.ServiceRunner.RunAsync(CancellationToken cancellationToken)    at ESR.Hosting.RunService.StartAsync(CancellationToken cancellationToken)
<6>ESR.Hosting.RunService[0] Service runner shutting down...

Testing a Connection

  1. Locate the section Test Connection Local Development
  2. Under the field/property commandLineArgs
  3. It should say --isLocal true --testingConnection true for the connection in test-settings.json to be tested.

Command

dotnet run --launch-profile "Test Connection Local Development"

This or any profile can be run via the IDE of your choosing as well. Feel free to use which one you are comfortable with.

Expected Results on Success

Local development is currently configured. You'll need to use the Xchange.Connector.SDK.Test.Local library
[09:55:33 INF] Connector (1.2.3.0) runtime started in 1.5096147 seconds
[09:55:36 INF] The connection test was successful
[09:55:36 INF] Service runner shutting down...

Token Storage

If you're working with OAuth2CodeFlow and want to run or test your connector locally, you will have to work with Token Storage.

When your connection is triggered for the first time, a pop up window will open; this will prompt you to login. In the process of authentication, a code will be exchanged for a token. If the authentication is successful, the response should include an access_token and a refresh_token. The response should be a variation of this:

```json
{
   "access_token":"<access_token>",
   "token_type":"Bearer",
   "expires_in":3600,
   "refresh_token":"<refresh_token>"
 }
```

When working locally, this response will be stored in a new folder called Token Storage, located inside the /bin folder of your connector.

The generated file with the response will be used in future transactions; the OAuth2CodeFlowHandler will get the access_token and using it to authenticate the request. If the authentication fails (a code of 401: Unauthorized is returned), the handler will use the refresh_token to reauthenticate.

Resetting Local Cache

If you need to reset the local databases (See Emulation Databases) this command will do so:

Command

xchange test reset

If you're using OAuth2CodeFlow, this command will also reset the Token Storage folder.

Emulation Databases

When you run a Connector locally there is no interaction with Xchange platform, only the target API the Connector is connecting against is interacted with.

There are two databases that are created:

  • Record cache
    • This is found at databases/00000000-0000-0000-0000-000000000000.db
    • The purpose of this database is store records that where sent to the App Network emulation. For example this powers the "App Network sync summary" that you'll see after a data reader finishes during a cache write service run.
  • Change detection
    • This is found at databases/Ryvit/{connector-key}/123/diffyV3.1/{module-key}_v{module-version}_{data-object-key}
    • The purpose of this database is to track a hash of all records passed through a Connector. This has is used to infer if a record is new (insert) or being updated. Additionally, if a record is known in the database but during a full data reader run is not yielded, then the change detection database will infer that the record should be deleted.

These databases are SQLite and thus for auditing and debugging purposes can be inspected to validate or troubleshoot test runs.

Summary

At this point, you should have used the test init command to pre-populate a test-settings.json file and configured it for testing, and run the test according to launchSettings.json with your IDE or command line.

You can repeat the build -> test init -> configure -> launch process as you complete your connector.

Note

Starting from version 1.3.6 of the CLI doing a build first is no longer required. xchange test init will trigger a build automatically