# Available Clients in SmileHarness

The `SmileHarness` interface provides access to various clients that can be used to interact with different aspects of a Smile CDR instance. This document provides detailed information about these clients, their capabilities, and how to use them effectively.

## FHIR Clients

FHIR clients are used to interact with FHIR resources on the Smile CDR server. The `SmileHarness` interface provides several methods to obtain FHIR clients with different configurations.

**Standard FHIR Client**

```java
IGenericClient fhirClient = harness.getFhirClient();
```

Copy

This method returns a standard FHIR client that can be used to interact with the FHIR endpoint. By default, this client does not include any authentication, so you may need to add an interceptor for authenticated requests.
**Variants:**

- **By Port**: `getFhirClient(int thePort)` \- Get a FHIR client for a specific port
- **By Module ID**: `getFhirClient(String theModuleId)` \- Get a FHIR client for a specific module

**Superuser FHIR Client**

```java
IGenericClient superuserFhirClient = harness.getSuperuserFhirClient();
```

Copy

This method returns a FHIR client with superuser (admin) credentials already configured. This is useful for administrative tasks or tests that require elevated privileges.
**Variants:**

- **By Port**: `getSuperuserFhirClient(int thePort)` \- Get a superuser FHIR client for a specific port
- **By Module ID**: `getSuperuserFhirClient(String theModuleId)` \- Get a superuser FHIR client for a specific module

**FHIR Client Capabilities**
The FHIR clients returned by these methods are instances of HAPI FHIR's `IGenericClient`, which provides a comprehensive API for interacting with FHIR resources. Some common operations include:

- **Create**: `client.create().resource(resource).execute()`
- **Read**: `client.read().resource(ResourceType.class).withId(id).execute()`
- **Update**: `client.update().resource(resource).execute()`
- **Delete**: `client.delete().resourceById(resourceType, id).execute()`
- **Search**: `client.search().forResource(resourceType).where(criteria).execute()`
- **Transaction**: `client.transaction().withBundle(bundle).execute()`

For more information on using HAPI FHIR clients, see the  [HAPI FHIR documentation](https://hapifhir.io/hapi-fhir/docs/client/generic_client.html).

## Admin JSON Client

The Admin JSON client is used to interact with the Smile CDR administrative API, which provides access to configuration, monitoring, and management functions.

```java
AdminJsonRestClient adminClient = harness.getAdminJsonClient();
```

Copy

**Variants:**

- **By Port**: `getAdminJsonClient(int thePort)` \- Get an Admin JSON client for a specific port

**Admin JSON Client Capabilities**
The `AdminJsonRestClient` provides methods for interacting with various aspects of the Smile CDR administrative API:

- **Node Configurations**: `adminClient.getNodeConfigurations()`
- **Module Information**: `adminClient.getModuleInfo(moduleId)`
- **Port Information**: `adminClient.getPortFromModule(moduleId)`
- **Module Configuration**: `adminClient.updateModuleConfig(nodeId, moduleId, options, restart, reload)` (waits for restart by default; overload accepts an explicit `shouldWaitForRestart` flag)
- **Module Restart**: `adminClient.restartModule(nodeId, moduleId)` (waits for restart by default; overload accepts an explicit `shouldWaitForRestart` flag)
- **User Management**: Methods for creating, updating, and deleting users
- **Role Management**: Methods for managing roles and permissions
- **System Status**: Methods for checking system health and status

The Admin JSON client is particularly useful for:

- Verifying configuration settings
- Managing users and permissions
- Monitoring system health
- Performing administrative tasks

**Waiting for Module Restart**
Both `updateModuleConfig(...)` and `restartModule(...)` **wait for the module to come back up by default**. The short-form overloads block until the target module's first process reports `STARTED`, so tests can exercise the module immediately after the call returns without racing the restart. Callers that want the old fire-and-forget behavior use the explicit `boolean shouldWaitForRestart` overloads.

```java
// Default — block until the module is STARTED again.
adminClient.restartModule(nodeId, moduleId);
adminClient.updateModuleConfig(nodeId, moduleId, options, /*restart*/ true, /*reload*/ false);

// Opt out of the wait (fire-and-forget — return as soon as the admin API accepts the request).
adminClient.restartModule(nodeId, moduleId, /*shouldWaitForRestart*/ false);
adminClient.updateModuleConfig(nodeId, moduleId, options, /*restart*/ true, /*reload*/ false, /*shouldWaitForRestart*/ false);

// updateModuleConfig only polls when either 'restart' or 'reload' is also true — otherwise wait is a no-op.

// Override the 90s default with a custom wait timeout (e.g. a longer budget for slow CI).
adminClient.restartModule(nodeId, moduleId, /*shouldWaitForRestart*/ true, Duration.ofMinutes(3));
adminClient.updateModuleConfig(nodeId, moduleId, options, /*restart*/ true, /*reload*/ false, /*shouldWaitForRestart*/ true, Duration.ofMinutes(3));
```

Copy

Polling behavior:

- The client polls `getNodeStatuses()` every 500 ms for up to 90 seconds.
- If the module reports the terminal failure statuses `FAILED_TO_START` or `FAILED_TO_STOP`, an `IllegalStateException` is thrown immediately rather than waiting for the timeout.
- If the timeout elapses before the module reaches `STARTED`, an `IllegalStateException` is thrown reporting the last observed status and the number of poll attempts.
- Transient errors contacting the admin endpoint are swallowed and retried until the deadline; the last error is included in the timeout exception if the module never becomes ready.

Pass `false` when the test does not need to interact with the module immediately after restart, or when you want the caller to be responsible for waiting (e.g. via a custom readiness check). Passing `true` is the recommended default for most integration tests — it removes the need for ad-hoc `Awaitility` blocks around module restarts.

## HL7v2 REST Client

The HL7v2 REST client is used to interact with the Smile CDR HL7v2 endpoint, which allows sending and receiving HL7v2 messages.

```java
HL7V2RestClient hl7v2Client = harness.getHL7V2RestClient();
```

Copy

**Variants:**

- **By Port**: `getHL7V2RestClient(int thePort)` \- Get an HL7v2 REST client for a specific port
- **By Module ID**: `getHL7V2RestClient(String theModuleId)` \- Get an HL7v2 REST client for a specific module

**HL7v2 REST Client Capabilities**
The `HL7V2RestClient` provides methods for sending HL7v2 messages to the Smile CDR server:

- **Send Message**: `hl7v2Client.sendMessage(message)`
- **Send Message with Options**: Methods for sending messages with specific options or headers

This client is particularly useful for testing HL7v2 integration scenarios, such as:

- Sending ADT (Admission, Discharge, Transfer) messages
- Testing HL7v2 to FHIR conversion
- Verifying HL7v2 message processing

## FHIR Context

While not a client per se, the `SmileHarness` also provides access to the FHIR context, which is useful for working with FHIR resources programmatically.

```java
FhirContext fhirContext = harness.getFhirContext();
```

Copy

**Variants:**

- **By Module ID**: `getFhirContext(String moduleId)` \- Get the FHIR context for a specific module

**FHIR Context Capabilities**
The `FhirContext` provides various utilities for working with FHIR resources:

- **Parsing**: Convert between FHIR resources and their JSON/XML representations
- **Validation**: Validate FHIR resources against the FHIR specification
- **Resource Creation**: Create new FHIR resources programmatically
- **Client Creation**: Create new FHIR clients

## Outbound SMART Client

The Outbound SMART client is used to perform OAuth 2.0 SMART on FHIR authorization flows for testing outbound authentication scenarios.

```java
OutboundSmartClient smartClient = harness.getOutboundSmartClient();
```

Copy

**Variants:**

- **By Port**: `getOutboundSmartClient(int thePort)` \- Get an Outbound SMART client for a specific port
- **By Module ID**: `getOutboundSmartClient(String theModuleId)` \- Get an Outbound SMART client for a specific module

**Outbound SMART Client Capabilities**
The `OutboundSmartClient` provides methods for OAuth 2.0 authorization flows:

**Implemented Methods:**

- **Token Exchange**: `exchangeCodeWithSecretHttpBasic()` \- Exchange authorization codes for access tokens with flexible authentication
- **Complete Authorization Flow**: `oauthAuthorizationCodeFlow()` \- Perform full OAuth flow including login
- **Authorization Code Acquisition**: `getAuthorizationCode()` \- Get authorization codes through the OAuth flow
- **Authentication**: `loginAdmin()`, `loginWithPassword()` \- Handle form-based authentication
- **Client Management**: `createOidcClient()`, `createClient()` \- Create and configure OAuth clients
- **Utility Methods**: `buildScopeString()`, `extractCodeFromUrl()` \- Helper methods for OAuth operations

**API Stubs (Not Yet Implemented):**

- **Authorization Code Flow**: `requestAuthorizationCode()` and `requestAuthorizationTokenWithCode()`
- **Client Credentials Flow**: `requestAuthorizationClientToken()`
- **Combined Flow**: `requestAuthorizationUserToken()` \- combines authorization code steps
- **Token Refresh**: `refreshToken()`

This client is particularly useful for testing:

- SMART on FHIR authorization workflows
- OAuth 2.0 token exchange operations
- Form-based authentication scenarios
- Client credentials management
- Integration with external authorization servers

## NPM Package Client

The NPM Package client is used to interact with Smile CDR's package registry endpoint, which provides access to FHIR Implementation Guide and package management functionality.

This tool is meant for uploading Implementation Guides or other FHIR Packages to the repository. You can find out more about creating your own packages [here](/content/docs/smileutil/create_package.html#smileutil-create-fhir-package/index.html).

```java
NpmPackageClient npmClient = harness.getNpmPackageClient();
```

Copy

**Variants:**

- **By Port**: `getNpmPackageClient(int thePort)` \- Get an NPM Package client for a specific port
- **By Module ID**: `getNpmPackageClient(String theModuleId)` \- Get an NPM Package client for a specific module

**NPM Package Client Capabilities**
The `NpmPackageClient` provides methods for installing FHIR packages on the Smile CDR server:

- **Install Package (Object)**: `npmClient.installPackageBySpec(packageSpec)` \- Install a package using a PackageInstallationSpec object
- **Install Package (JSON)**: `npmClient.installPackageBySpec(jsonString)` \- Install a package using a JSON string specification

**Using PackageSpecBuilder**
The `PackageSpecBuilder` provides a fluent API for creating package installation specifications:

```java
PackageInstallationSpec spec = PackageSpecBuilder
    .name("test-profile")
    .version("1.0.0")
    .packageUrl("classpath:/test_ig.tgz")
    .storeOnly()
    .build();

PackageInstallOutcomeJson outcome = npmClient.installPackageBySpec(spec);
```

Copy

**Alternative JSON String Usage:**

```java
String packageSpecJson = """
    {
        "name": "my-implementation-guide",
        "version": "1.0.0",
        "packageUrl": "classpath:/my_ig.tgz",
        "installMode": "STORE_AND_INSTALL",
        "reloadExisting": false,
        "fetchDependencies": true,
        "installResourceTypes": ["StructureDefinition", "ValueSet"]
    }
    """;

PackageInstallOutcomeJson outcome = npmClient.installPackageBySpec(packageSpecJson);
```

Copy

**Builder Methods:**

- `name(String)` \- Set the package name (required)
- `version(String)` \- Set the package version (required)
- `packageUrl(String)` \- Set the package URL (optional)
- `storeOnly()` \- Set install mode to STORE\_ONLY
- `storeAndInstall()` \- Set install mode to STORE\_AND\_INSTALL
- `reloadExisting(boolean)` \- Set whether existing resources should be reloaded
- `fetchDependencies(boolean)` \- Set whether dependencies should be automatically resolved and installed
- `installResourceTypes(String...)` \- Set specific resource types to install from the package
- `build()` \- Build the PackageInstallationSpec

**Advanced Configuration Example:**

```java
PackageInstallationSpec spec = PackageSpecBuilder
    .name("advanced-profile")
    .version("2.0.0")
    .packageUrl("classpath:/advanced_ig.tgz")
    .storeAndInstall()
    .reloadExisting(false)
    .fetchDependencies(true)
    .installResourceTypes("StructureDefinition", "ValueSet", "CodeSystem")
    .build();
```

Copy

This client is particularly useful for testing:

- FHIR Implementation Guide installation
- Package registry functionality
- Package validation and storage
- Package dependency management

## Client Lifecycle and Ownership

Every client a harness hands out issues on the harness's own HTTP client, so they share one connection pool and one cookie store. A login performed through one is visible to the next; see [Sessions, Cookies and Harness Lifecycle](/content/docs/test_tools/usage.html#sessions-cookies-and-harness-lifecycle/index.html).

`AdminJsonRestClient`, `HL7V2RestClient` and `NpmPackageClient` are `AutoCloseable`, and what `close()`
does depends on where the client came from:

- **From the harness** (`harness.getAdminJsonClient()`, `harness.getHL7V2RestClient()`, …) the client
borrows the harness's pool. Closing it is a no-op, so a `try`-with-resources around one will not shut
down the harness the rest of your test is using.
- **Opened directly** (`AdminJsonRestClient.open(baseUrl, username, password)`) the client owns the pool
it created, and `close()` releases it. Close these, or each `open(...)` leaves a pool running for the
life of the JVM. The `open` prefix is the signal: anything you `open` is yours to `close`.

To build several clients directly and have them share one pool and one session, create a
`SmileTestHttpClient` yourself and pass it to each — you own it, so close it when you are done, and the clients built over it need no closing of their own:

```java
try (SmileTestHttpClient httpClient = SmileTestHttpClient.create()) {
    AdminJsonRestClient adminClient = AdminJsonRestClient.issuingOn(httpClient, baseUrl, "admin", "password");
    NpmPackageClient npmClient = NpmPackageClient.issuingOn(httpClient, baseUrl, "admin", "password");
    // a session established through adminClient is visible to npmClient
}
```

Copy

`open(...)`, `openAnonymous(...)` and `issuingOn(...)` all exist on `AdminJsonRestClient`,
`HL7V2RestClient` and `NpmPackageClient`, so the name tells you at the call site which of the two owns the pool. Working through a `SmileHarness` needs none of this — the harness already shares its client with everything it hands out.

The older `build(...)` and `buildAnonymous(...)` factories still work and are deprecated, because neither name said who owned the pool. Replace `build(url, user, pass)` with `open(...)` and
`buildAnonymous(url)` with `openAnonymous(...)`.

Redirects are not followed by any of these clients, so a `3xx` is returned rather than resolved.

## Future Clients

The `SmileHarness` interface includes comments about potential future clients that may be added:

- **Camel Direct**: For interacting with Camel routes
- **Broker Sender**: For sending messages to the broker
- **Smile Log Consumer**: For consuming logs from Smile CDR
- **CDA Tooling**: For working with CDA documents
- **OpenTelemetry Integration**: For working with OpenTelemetry
- **Data Seeding**: For seeding test data
- **Subscription REST Hook Receiver**: For receiving subscription notifications

These clients are not yet implemented but may be added in future versions of the library.

## Best Practices

When working with these clients, consider the following best practices:

1. **Reuse Clients**: Create clients once and reuse them throughout your tests to avoid unnecessary overhead.
2. **Close Resources**: Make sure to close any resources (like input streams) that you open.
3. **Handle Exceptions**: Properly handle exceptions that may be thrown by client methods.
4. **Use Appropriate Authentication**: Use the appropriate level of authentication for your tests.
5. **Clean Up**: Clean up any resources you create during tests to avoid affecting other tests.

For more recommendations on effective testing with `SmileCdrContainer` and `SmileHarness`, see [Best Practices](/content/docs/test_tools/bestpractices.html).

[Usage](/content/docs/test_tools/usage.html)

Clients

Testing Tools

[Overview](/content/docs/test_tools/overview.html)
[Usage](/content/docs/test_tools/usage.html)
[Clients](/content/docs/test_tools/clients.html)
[Best Practices](/content/docs/test_tools/bestpractices.html)

[Best Practices](/content/docs/test_tools/bestpractices.html)
