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.publishpermission, 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
- 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:
| Kind | Before a handler runs |
|---|---|
| HTTP | Nothing. Every handler, writes included, runs against the real system as soon as you run it. |
| SCIM | Nothing, as for HTTP. |
| LDAP | A 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. |
| SQL | Every 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).
- 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.
- Select Test Connection. It runs
connection.testConnectionwith that configuration. - 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.
- 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
egressand the hosts of literal URLs in its authentication; - the hosts that an authentication
base_urlortoken_urlexpression produces from your saved configuration, such asacme.acmeprojects.examplefor'https://' + config.subdomain + '.acmeprojects.example'; - the
egress_domainsin 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.previewTestHostsandconnectors.runTest, described below.connectors.testArtifactrefuses 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:
-
connectors.previewTestHostswith{ "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. -
connectors.runTestruns 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_jsonandfilesare the draft content to test, saved or not, and there is no field for credentials or extra hosts.confirmed_hostsis the list you got frompreviewTestHosts. 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 newidempotency_key. Otherwise the run executes, and the answer is a receipt withreceipt_id,status(succeeded,failedorunknown),hosts,blocked_hostsand aresultholdingok,data,error,logs,blocked_egress,attempted_hostsandduration_ms, with credentials removed.unknownmeans 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:
| Case | Expect |
|---|---|
connection.testConnection with valid credentials | Success |
connection.testConnection with a wrong credential | A failure whose message tells the admin what to fix |
<entity>.fetchAll | Records, and a next cursor while more remain |
<entity>.fetchAll with a small page size, followed through every page | Every record exactly once, and the walk ends |
<entity>.fetchOne for a record that exists, and for one that does not | The 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.checkConformanceanswersok: true. - Your
README.mdlists what you confirmed on the test account and what remains an assumption.