Skip to main content

Test your connector

Building a connector, step 8 of 10. Previous: the last page of your kind's track (HTTP, LDAP, SCIM or SQL; a flat-file connector continues from its track straight to publishing).

Testing a connector means running its operations against the real system and checking that each one does what your design table says. Every test run in Owlie is live: it builds your draft exactly as publishing would and calls the system with the credentials you give it. Build and test against a sandbox or test tenant of the vendor, with test accounts you can change freely, never your production tenant.

Before you test​

  • A test account in the target system, with an admin credential that has the permissions your connector declares.
  • For LDAP and SQL connectors, a connected private-network gateway whose operator has approved this connector and allowed the directory or database host, with its port (Settings custom connectors depend on). Test runs for these kinds happen on the gateway, and they also need:
    • the custom_connector.publish permission, because running unpublished code on your gateway carries the same trust as publishing it (the owner and admin roles have it by default);
    • credentials typed into the test configuration. A draft test never reads the gateway's own secrets, so a gateway secret name is refused with draft.secret-ref. Installs use those secrets.
  • The design table from Model Grant kinds. Each row with a write becomes a set of test cases.

What happens before a run that can change the system depends on the kind:

KindBefore a handler runs
HTTPNothing. Every handler, writes included, runs against the real system as soon as you run it.
SCIMNothing, as for HTTP.
LDAPA create, update, delete, enable, disable, Grant add or remove, custom operation or PowerShell script runs only after you confirm it. Reads and connection operations run without asking.
SQLEvery handler runs only after you confirm it, reads and connection.testConnection included, because the gateway runs your statements as written. Only connection.validateConfig and connection.validateStatements run without asking.

An HTTP connector whose execution_targets is ["gateway"] also runs its tests on the gateway, with the LDAP rules and the same permission and credential requirements. The builder does not ask to confirm its writes, so run those through the API with "allow_writes": true.

In the Connector Builder​

Open the dock with the Test tab (⌘B on macOS, Ctrl+B elsewhere).

  1. Select Run config and enter the configuration and credentials the test should use. Credentials entered here are used for test runs only; they are never exported, and the API and AI agents cannot read them back.
  2. Select Test Connection. It runs connection.testConnection with that configuration.
  3. Choose a handler, enter its input, and select Run (⌘↵ on macOS, Ctrl+Enter elsewhere). The result, the handler's log lines and the time it took appear below. If the handler tried to reach a host the connector is not allowed to reach, the result says which. A run on a gateway (LDAP and SQL) returns the result and the time only, without log lines or hosts.
  4. Select Save input to keep the input under Saved inputs for that handler. Saved inputs are stored with the draft (in fixtures/inputs.json), so they survive and travel with an export. Run all runs every saved input for the handler, and the badge shows how many succeed.

For an LDAP or SQL connector, a run that needs confirmation (see the table above) first asks Run <key> against the real system?. Choose Run it to go ahead. HTTP and SCIM handlers never ask.

The Problems tab lists what would stop the draft from publishing, such as a missing handler or an invalid manifest.

Runs of an HTTP connector​

An HTTP connector that runs in Owlie's cloud tests through its saved Run config only. Before each run the panel saves your draft, the Run config included, and the run reads its credentials from what was saved. Nothing you type in the browser goes to the run any other way, and a request cannot add hosts or credentials of its own. Each click on Run or Test Connection starts one run. Run all starts one run for each saved input, and stops if the hosts change.

Above Run, Run may contact lists the exact hosts the run is allowed to reach. Owlie builds the list from:

  • the domains in the manifest's egress and the hosts of literal URLs in its authentication;
  • the hosts that an authentication base_url or token_url expression produces from your saved configuration, such as acme.acmeprojects.example for 'https://' + config.subdomain + '.acmeprojects.example';
  • the egress_domains in your saved Run config.

A host that no earlier test run of this connector has reached is marked new. An entry that cannot become a host is listed with the reason it was ignored: a wildcard, an IP address, localhost or a name with a single label. A run may list at most 20 hosts.

A test run reaches exactly these hosts. Unlike a published connector, it does not reach their subdomains, and that holds for redirects and for the request that fetches a token. A domain in egress such as acmeprojects.example therefore lets a test reach that domain and not acme.acmeprojects.example. Write the base URL as an expression over a configuration value, or list the full host under Additional allowed domains on the Basics tab.

If the hosts change between the moment the list appeared and your click, for example because you edited the Run config, nothing runs. The panel shows the new list, with a notice, and the next click on Run confirms it. A host that the handler tries to reach and is not on the list is blocked and appears under Blocked egress in the result.

Owlie can prepare a run for you when you build with Owlie. A prepared run opens in this panel with the handler and input filled in. It runs only when you press Run, and Continue without testing dismisses it.

Through the API​

Operations on POST /api/rpc (the same endpoint the builder uses; authenticate with an API key, see Build with the API and agents) run tests. Which one you call depends on where the connector runs:

  • An HTTP connector that runs in Owlie's cloud uses connectors.previewTestHosts and connectors.runTest, described below. connectors.testArtifact refuses its drafts.
  • SCIM, LDAP and SQL connectors, and an HTTP connector limited to a gateway, use connectors.testArtifact.

connectors.testArtifact runs one handler: { "connector_id", "version_id", "artifact_key": "user.fetchOne", "test_input", "integration_config" }. It answers { ok, data, error, logs, blocked_egress, attempted_hosts, duration_ms }. For a connector that runs on a gateway, add "gateway_id" to choose the gateway, and add "allow_writes": true to run a handler that needs confirmation (see the table above). A SCIM connector runs in Owlie's cloud and ignores allow_writes; its handlers always run.

connectors.checkConformance answers { ok, required, issues }. It checks the manifest and the handler set the way publishing does. Publishing also checks the files and compiles them, so a draft that passes conformance can still fail to publish.

The request body is { "procedure": "connectors.testArtifact", "input": { ... } }, and a successful response wraps the answer as { "result": { ... } }. A handler that fails still answers HTTP 200, with ok: false and the failure in error. A request Owlie refuses answers an error status instead; Build with the API and agents shows its shape and has complete requests. integration_config is the whole install configuration the handler would see: { "config": { ... }, "auth": { "key": "<option>", "config": { ... } } }. Owlie fills in no defaults for a test: send every configuration value the install would hold, including the ones an LDAP, SCIM, SQL or flat-file connector's install form derives from your definition (such as a SQL connector's dialect and every install-editable value). Only a secret you leave blank is taken from the run config saved in the builder, if there is one.

Run an HTTP connector's handler​

An HTTP run reads its configuration and credentials from the Run config saved with the draft, as the builder's Test tab does. Save it first with updateCustomConnectorDraft and its test_run_config_json input, which has the shape of integration_config above and is write-only: no call returns it. Then:

  1. connectors.previewTestHosts with { "connector_id", "version_id", "manifest_json" } answers { hosts, ignored, new_since_last_run }: the exact hosts a run would reach now, the entries that could not become hosts, and the hosts no earlier run of this connector reached.

  2. connectors.runTest runs one handler:

    {
    "procedure": "connectors.runTest",
    "input": {
    "connector_id": "<connector id>",
    "version_id": "<draft version id>",
    "manifest_json": { "...": "the manifest to test" },
    "files": { "handlers/user/fetchAll.ts": "..." },
    "artifact_key": "user.fetchAll",
    "test_input": { "pageSize": 10 },
    "confirmed_hosts": ["api.acmeprojects.example"],
    "idempotency_key": "<a new unique value for this run>"
    }
    }

    manifest_json and files are the draft content to test, saved or not, and there is no field for credentials or extra hosts. confirmed_hosts is the list you got from previewTestHosts. If the hosts resolved when the call arrives differ from it, nothing runs and the answer is { "status": "hosts_changed", "hosts", "ignored" }: check the new list and send the call again with it and a new idempotency_key. Otherwise the run executes, and the answer is a receipt with receipt_id, status (succeeded, failed or unknown), hosts, blocked_hosts and a result holding ok, data, error, logs, blocked_egress, attempted_hosts and duration_ms, with credentials removed. unknown means the request may have been sent and no answer came back; do not retry it automatically.

An idempotency_key works once. Sending it again answers with the same receipt, without result, and does not run the handler again. Only a draft version can be tested, and custom_connector.modify is enough.

What to test​

Work through your design table. For every entity:

CaseExpect
connection.testConnection with valid credentialsSuccess
connection.testConnection with a wrong credentialA failure whose message tells the admin what to fix
<entity>.fetchAllRecords, and a next cursor while more remain
<entity>.fetchAll with a small page size, followed through every pageEvery record exactly once, and the walk ends
<entity>.fetchOne for a record that exists, and for one that does notThe record; then { "record": null }
<entity>.fetchRelationships (or fetchAll for embedded Grant kinds)One relationship per Grant, each naming both ends by their external ids

An LDAP connector pages with the pageSize in its definition and ignores a page size in the test input. Its paging position lives on the gateway's directory connection for one run only, so a later test run cannot continue from the cursor an earlier one returned. Set a small pageSize to see that a page ends with a cursor, and check the complete walk with a Sync after you install.

For the account entity, run each lifecycle operation your connector has (create, update, disable, enable, delete) on a test account, then fetchOne to check the result and the lifecycle state.

For every writable Grant kind, run the eight cases in Grant write contract. The two that catch the worst mistakes:

  • Revoke a Grant that exists and check it is gone in the vendor's admin console. A remove that calls the wrong URL answers 404, and a connector that trusts every 404 reports "already gone" while the access remains.
  • Revoke for a user who does not exist. The remove must fail. A connector that reports "already gone" here would record revokes that never happened.

Save each case's input as you go, so Run all repeats the whole set after every change.

Saved inputs​

fixtures/inputs.json holds the saved inputs, keyed by handler key. Each entry has a name and the handler's input; the builder adds an id and the latest result (last_result) when it saves or runs one. An agent can write this file with the draft so a person can rerun the same cases in the builder:

{
"user.fetchOne": [
{ "name": "Existing user", "input": { "identifiers": { "id": "usr_1001" } } },
{ "name": "Missing user", "input": { "identifiers": { "id": "usr_0000" } } }
]
}

The file must be a JSON object whose values are lists, each entry an object whose input is an object. While it is not, the builder shows it as it is and refuses to save more inputs until it is fixed.

Limits​

A connector that runs in Owlie's cloud gets at most 10 seconds of CPU time and 1,000 outbound requests per invocation, test runs included, and each HTTP request times out after 20 seconds unless the handler sets its own timeout. The Handler API has the details.

Before you publish​

  • Every case in your design table has been run against the test account, and the results match.
  • Problems is empty, or connectors.checkConformance answers ok: true.
  • Your README.md lists what you confirmed on the test account and what remains an assumption.

Next: Publish, install and upgrade