Skip to content

test(connections): add Connections integration tests [APPS-37829] - #761

Open
anil-uipath wants to merge 1 commit into
mainfrom
feat/is-integ-tests
Open

anil-uipath wants to merge 1 commit into
mainfrom
feat/is-integ-tests

Conversation

@anil-uipath

@anil-uipath anil-uipath commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds live-API integration suites for the four Integration Service services already on main (Connections #554, Connectors #555, Elements #556, Execute), and wires the fixtures they need through the integration harness and CI.

No src/ changes — this is test-only. There are no new methods, endpoints, transforms or OAuth scopes, so the usual Method Added / Endpoint Called / API Response vs SDK Response sections don't apply; the equivalent information for a test PR is below.

Suites Added

Suite File Tests SDK surface covered
Connections connections.integration.test.ts 4 getAll(), getById()
Connectors connectors.integration.test.ts 7 getAll(), getById(), getConnections(), getDefaultConnection()
Elements elements.integration.test.ts 10 getObjects(), getActivities(), getObjectMetadata(), getEventObjects(), getEventObjectMetadata(), getInstanceObjects(), getInstanceObjectMetadata(), getInstanceEventObjects(), getInstanceEventObjectMetadata()
Execute execution.integration.test.ts 2 execute()

Coverage is scoped to the API surface that actually exists on main. Connections.ping() and Connections.reauthenticate() were both removed upstream (#698 and 1b8f68ebe), so the suites exercise getAll / getById only — the entity has no bound methods to assert.

Credential Requirement

All four suites are declared describeIntegration(name, 'user', modes, body).

Integration Service declares bearerFormat: JWT in its own swagger and validates the JWT locally. The test tenant issues PATs in opaque rt_ form, so the token fails at parse before any scope check — a real PAT, a garbage string, and no Authorization header at all return byte-identical 401 {"code":"Unauthorized"}. A token that was being introspected but lacked a scope would return 403 / insufficient_scope instead.

Consequence for the credential matrix in coverage.yml:

Leg Behaviour
pat skipped — the credential cannot authenticate against connections_ at all
user runs under UIPATH_USER_TOKEN (minted by Minter in CI)

This follows the 'user' precedent already in the repo for insightsrtm_ (Agents, Agent Memory, Agent Traces, Governance) and the notification service, per agent_docs/rules.md: "When a service rejects PAT auth, run it under a user token — do not describe.skip it."

How to Run

# 1. Mint a user token (see tests/integration/README.md § Getting a user token)
az acr login -n pltnonprodacr
docker run --rm -v "$PWD/out:/out" pltnonprodacr.azurecr.io/uipath-minter:latest \
  npm run generate -- -u <email> -p <password> -n <org> -t <tenant> -e <env> -v basic -o /out/tokens.json

# 2. Put the accessToken into tests/.env.integration as UIPATH_USER_TOKEN,
#    alongside the Integration Service fixtures

# 3. Run just these suites
npx vitest run --config vitest.integration.config.ts tests/integration/shared/integration-service/

Without UIPATH_USER_TOKEN the suites report as skipped rather than failing, which is what a PAT-only developer machine looks like by default.

Fixtures

Env var Purpose Required
INTEGRATION_SERVICE_TEST_CONNECTOR_KEY Connector key, e.g. uipath-salesforce-slack Connectors, Elements, Execute
INTEGRATION_SERVICE_TEST_CONNECTION_ID Pre-existing connection id on that connector Connections, Elements, Execute
INTEGRATION_SERVICE_TEST_EVENT_OPERATION Event operation name Elements
INTEGRATION_SERVICE_TEST_OBJECT_NAME Object name Optional — resolved from the connector (Elements) or the connection (Execute) when unset

coverage.yml passes all four through as UIPATH_INTEGRATION_SERVICE_TEST_*_DEV || UIPATH_INTEGRATION_SERVICE_TEST_*.

Important

Three repository secrets must be created before this merges. Until they exist, the user leg reaches beforeAll and throws INTEGRATION_SERVICE_TEST_… must be set:

  • UIPATH_INTEGRATION_SERVICE_TEST_CONNECTOR_KEY_DEV
  • UIPATH_INTEGRATION_SERVICE_TEST_CONNECTION_ID_DEV
  • UIPATH_INTEGRATION_SERVICE_TEST_EVENT_OPERATION_DEV

OBJECT_NAME may be left unset. The values point at resources in the alpha test tenant, so only the _DEV variants are meaningful.

Verification

Check Result
npm run typecheck clean
npm run lint 0 errors
npm run test:unit 2873 passed
npm run build exit 0
Suites on a PAT-only env 4 skipped (4) / 23 of 23 tests skipped
Suites with a user token configured all 23 collect as [v1][user] and reach the API

tsconfig.json sets "exclude": [..., "tests", ...], so npm run typecheck does not cover integration tests. These were additionally typechecked against a config that includes tests/integration/**, which is how the API-surface mismatches above were found. That run is clean for these files; the 20 errors it reports elsewhere (Maestro suites, tests/utils/mocks/entities.ts, tests/utils/mocks/functions.ts, auth-errors) are pre-existing on main and out of scope here — worth a follow-up, since nothing in CI currently sees them.

A convention review pass against agent_docs/ was run over the diff and its findings folded into this commit: the is*-prefixed config fields were renamed to mirror their env vars 1:1 (integrationServiceTestConnectorKey etc., since isConnectionId reads as a boolean predicate), the Elements event-operation guard was consolidated into beforeAll (it had been repeated in four it blocks, and eventObjectName! carried a definite-assignment assertion the code did not honour), the unfiltered connector listing was hoisted into beforeAll, and four assertions that could not fail were tightened — getDefaultConnection now asserts isDefault === true rather than its type, the mostRecentFirst test throws rather than passing vacuously on a single-element array, result.headers is checked for keys rather than typeof === 'object' (which passes for null), and two Elements test titles that promised unasserted fields were trimmed.

A second pass verified each of those fixes and found more: the CI fixture block was still splitting the IDENTITY_TEST_USER_ID / IDENTITY_MUTABLE_TEST_USER_ID pair in the .env heredoc (only the outputs block had been moved); neither Execute test could fail on a mis-built passthrough URL, since a 404 from a wrong endpoint constant satisfied both — the first now asserts status !== 404, which is the point of the suite; expect(Array.isArray(result)) was tautological in five Elements tests because the service returns response.data ?? [], so they now assert item shape; the Elements suite had no error-path coverage for any of its nine methods, so getObjectMetadata now has a reject test; two duplicate live lookups were hoisted into beforeAll; and a comment documenting the object-name precedence had it backwards.

Not yet run green against the live API — that needs a minted user token, which is only available in CI or from a local Minter run.

Files

Area Files
Integration tests tests/integration/shared/integration-service/connections.integration.test.ts (4 tests)
tests/integration/shared/integration-service/connectors.integration.test.ts (7 tests)
tests/integration/shared/integration-service/elements.integration.test.ts (10 tests)
tests/integration/shared/integration-service/execution.integration.test.ts (2 tests)
Test harness tests/integration/config/unified-setup.ts (registers the three services)
tests/integration/config/test-config.ts (four fixtures)
CI .github/workflows/coverage.yml (fixture pass-through)
Docs tests/integration/README.md, tests/.env.integration.example

Refs APPS-37829

🤖 Auto-generated using onboarding skills

@anil-uipath
anil-uipath requested a review from a team September 18, 2026 08:36
@anil-uipath anil-uipath changed the title test(integration-service): add Integration Service integration tests [JAR-IS-5] test(integration-service): add Integration Service integration tests [APPS-37829] Sep 18, 2026
@anil-uipath anil-uipath changed the title test(integration-service): add Integration Service integration tests [APPS-37829] test(integration-service): add Connections integration tests [APPS-37829] Sep 18, 2026
@anil-uipath anil-uipath changed the title test(integration-service): add Connections integration tests [APPS-37829] test(connections): add Connections integration tests [APPS-37829] Sep 18, 2026
Comment on lines +55 to +61
expect(Array.isArray(result)).toBe(true);
// Assert the ordering the flag requests, not just that a response arrived —
// a shape-only assertion passes even when the flag never reaches the API.
const timestamps = result.map((connection) => Date.parse(connection.createTime));
expect(timestamps.every((value) => Number.isFinite(value))).toBe(true);
const descending = [...timestamps].sort((a, b) => b - a);
expect(timestamps).toEqual(descending);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering assertion passes vacuously when result has 0 or 1 items — a single-element array can never be out of order, and an empty array trivially satisfies toEqual. Per rules.md: "Always throw new Error() when test preconditions are not met." Add a minimum-count guard before the sort:

Suggested change
expect(Array.isArray(result)).toBe(true);
// Assert the ordering the flag requests, not just that a response arrived —
// a shape-only assertion passes even when the flag never reaches the API.
const timestamps = result.map((connection) => Date.parse(connection.createTime));
expect(timestamps.every((value) => Number.isFinite(value))).toBe(true);
const descending = [...timestamps].sort((a, b) => b - a);
expect(timestamps).toEqual(descending);
expect(Array.isArray(result)).toBe(true);
if (result.length < 2) {
throw new Error(
'Need at least 2 connections to verify mostRecentFirst ordering — create more connections in the test tenant.',
);
}
// Assert the ordering the flag requests, not just that a response arrived —
// a shape-only assertion passes even when the flag never reaches the API.
const timestamps = result.map((connection) => Date.parse(connection.createTime));
expect(timestamps.every((value) => Number.isFinite(value))).toBe(true);
const descending = [...timestamps].sort((a, b) => b - a);
expect(timestamps).toEqual(descending);

});

it('getObjectMetadata should return an object with name + fields', async () => {
const result = await elements.getObjectMetadata(elementKey, objectName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test description says "name + fields" but only result.name is asserted — result.fields is never checked. Per rules.md: "Test descriptions must match what's being tested." Either assert on result.fields (e.g. expect(Array.isArray(result.fields)).toBe(true)) or trim the description:

Suggested change
const result = await elements.getObjectMetadata(elementKey, objectName);
it('getObjectMetadata should return an object with name', async () => {


it('getInstanceObjectMetadata should return an object with name', async () => {
const result = await elements.getInstanceObjectMetadata(connectionId, elementKey, objectName);
expect(result).toBeDefined();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

objectName was derived from elements.getObjects(elementKey) (connector-level enumeration, line 41) but getInstanceObjectMetadata is a connection-scoped API. If the connection only exposes a subset of the connector's objects, this call can fail with a non-obvious error when objects[0].name happens not to be available under the specific connection.

A safer approach: resolve objectName for the instance tests from getInstanceObjects(connectionId, elementKey) instead, mirroring what the execute suite does (execution.integration.test.ts line 49):

const instanceObjects = await elements.getInstanceObjects(connectionId, elementKey);
if (instanceObjects.length === 0) {
  throw new Error(`Connection ${connectionId} exposes no instance objects for ${elementKey}.`);
}
// Use connector-level objectName for static tests, connection-scoped for instance tests
const instanceObjectName = config.isObjectName ?? instanceObjects[0].name;

Then use instanceObjectName in getInstanceObjectMetadata and getInstanceObjects tests.

@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Three findings from this run.

New inline comments posted:

  1. connections.integration.test.ts line 55-61 — The mostRecentFirst ordering assertion passes vacuously when the API returns 0 or 1 item. A guard (if result.length < 2 throw) is needed to make the ordering check meaningful.

  2. elements.integration.test.ts line 73 — Test description says 'name + fields' but only result.name is asserted. Either add a result.fields assertion or trim the description to match what is actually tested.

  3. elements.integration.test.ts line 87 — getInstanceObjectMetadata is called with an objectName sourced from connector-level getObjects(), but the method is connection-scoped. If the connection exposes a different object set, the test fails non-obviously. Derive the name from getInstanceObjects(connectionId, elementKey) instead (same as the execute suite does).


it('getInstanceObjectMetadata should return an object with name', async () => {
const result = await elements.getInstanceObjectMetadata(connectionId, elementKey, objectName);
expect(result).toBeDefined();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four event tests each repeat the same if (!eventOperation) throw guard inline. Per agent_docs/rules.md:

"Consolidate service availability guards in beforeAll", not inline in each test"

Since these guards all live inside describe('events'), a beforeAll at the top of that block would consolidate them into a single check and let each it body drop its guard entirely:

Suggested change
expect(result).toBeDefined();
describe('events', () => {
beforeAll(() => {
if (!eventOperation) {
throw new Error(
'INTEGRATION_SERVICE_TEST_EVENT_OPERATION must be set to exercise event endpoints',
);
}
});

Each it body can then use eventOperation (and eventObjectName) without the repetitive guard.

});

it('should order results newest-first when mostRecentFirst is set', async () => {
const result = await connections.getAll({ pageSize: 3, mostRecentFirst: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering assertion is vacuously true when pageSize: 3 returns only one connection — a single-element array is always sorted. The mostRecentFirst flag would go completely unexercised in a tenant with a single connection.

Guard at the start of the test so the flag-vs-sort check is only reached when there are at least two data points:

Suggested change
const result = await connections.getAll({ pageSize: 3, mostRecentFirst: true });
it('should order results newest-first when mostRecentFirst is set', async () => {
const result = await connections.getAll({ pageSize: 3, mostRecentFirst: true });
expect(Array.isArray(result)).toBe(true);
if (result.length < 2) {
throw new Error(
'Test tenant has fewer than 2 connections — create at least two to validate mostRecentFirst ordering.',
);
}

@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review summary

Two new inline findings this run:

  1. elements.integration.test.ts line 87 — The four if (!eventOperation) throw guards inside describe('events') are repeated verbatim in each it block. CLAUDE.md rules require consolidating these into a beforeAll at the top of that describe block.

  2. connections.integration.test.ts line 53 — The mostRecentFirst ordering assertion is vacuously true when only one connection is returned. Need a result.length < 2 guard so the sort check is only reached when there are enough data points to be meaningful.

Everything else looks good: credential matrix, fixture wiring, CI pass-through, transform validation checks, and per-service beforeAll guards all follow established patterns.


const sample = result[0];
expect(typeof sample.id).toBe('string');
expect(typeof sample.name).toBe('string');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getById test validates transform completeness — camelCase fields present and PascalCase fields absent. The getAll test only validates camelCase presence. Per agent_docs/rules.md:

"getAll and getById need separate transform completeness tests — each has its own transformFn closure, so a getById test doesn't cover getAll."

Add the PascalCase absent assertions to the getAll shape check so both endpoints are independently verified:

Suggested change
expect(typeof sample.name).toBe('string');
expect(Object.values(ConnectionState)).toContain(sample.state);
// PascalCase API fields must be absent — validates the IS API returns camelCase verbatim
const raw = sample as unknown as Record<string, unknown>;
expect(raw.Id).toBeUndefined();
expect(raw.Name).toBeUndefined();
expect(raw.State).toBeUndefined();

const sample = result[0];
expect(typeof sample.id).toBe('number');
expect(typeof sample.key).toBe('string');
expect(typeof sample.name).toBe('string');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getById test validates camelCase fields present and PascalCase fields absent (with the comment "this validates the SDK returns the API shape verbatim"). The getAll test only checks field types. Per agent_docs/rules.md:

"getAll and getById need separate transform completeness tests."

Add PascalCase absent assertions to make getAll a complete transform validation test, consistent with getById:

Suggested change
expect(typeof sample.name).toBe('string');
expect(typeof sample.isPrivate).toBe('boolean');
// PascalCase keys must not be present — validates the IS API returns camelCase verbatim
const raw = sample as unknown as Record<string, unknown>;
expect(raw.Id).toBeUndefined();
expect(raw.Key).toBeUndefined();
expect(raw.Name).toBeUndefined();
expect(raw.IsPrivate).toBeUndefined();

@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Two new findings this run.

New inline comments posted:

  1. connections.integration.test.ts line 48 — The getAll test checks camelCase field types but doesn't verify PascalCase fields are absent. The getById test in the same file already includes this PascalCase absent check. Per rules.md, both need independent transform completeness tests since each exercises its own code path.

  2. connectors.integration.test.ts line 47 — Same gap: getAll validates field types but not PascalCase key absence, while getById already validates both. Add the same PascalCase absent assertions to the getAll shape check.

…[APPS-37829]

Adds live-API suites for Connections, Connectors, Elements and Execute, and
wires the fixtures they need through the integration harness and CI.

Integration Service validates JWTs locally (`bearerFormat: JWT` in its own
swagger), so an opaque `rt_` PAT fails at parse before any scope check and
every call returns a bare 401 identical to an anonymous request. All four
suites therefore declare `describeIntegration(..., 'user', ...)`: they run
under UIPATH_USER_TOKEN and report as skipped on a PAT-only machine rather
than failing.

- register ConnectionsService / ConnectorsService / ElementsService in
  unified-setup, and add the four INTEGRATION_SERVICE_TEST_* fixtures to
  test-config
- pass those fixtures through coverage.yml so the user leg of the credential
  matrix can run the suites
- document the credential requirement and the fixtures in the integration
  README and .env.integration.example

Refs APPS-37829

Co-Authored-By: Claude Opus 5 <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant