# Wiring Integration Test Secrets into Tests (Node.js)

Node.js integration tests use `@theorchard/backend-jwtauth-testing` to fetch secrets and generate bearer tokens. If your service is a GraphQL service or subgraph, you also need `@theorchard/graphql-integration`.

## Install the dependencies

Add `@theorchard/backend-jwtauth-testing` and `@theorchard/backend-secrets-manager-sdk-v3` to your dev dependencies:

```sh
# npm
npm install --save-dev @theorchard/backend-jwtauth-testing@^0.4.1 @theorchard/backend-secrets-manager-sdk-v3@^0.2.0

# yarn
yarn add --dev @theorchard/backend-jwtauth-testing@^0.4.1 @theorchard/backend-secrets-manager-sdk-v3@^0.2.0

# pnpm
pnpm add --save-dev @theorchard/backend-jwtauth-testing@^0.4.1 @theorchard/backend-secrets-manager-sdk-v3@^0.2.0
```

`@theorchard/backend-secrets-manager-sdk-v3` has a peer dependency on `@aws-sdk/client-secrets-manager ^3.x`. Before installing, check your existing AWS SDK version:

```sh
grep 'aws-sdk' package.json
```

For the exact installed version, check the lockfile:

```sh
# npm
grep '@aws-sdk/client-secrets-manager' package-lock.json

# yarn
grep '@aws-sdk/client-secrets-manager' yarn.lock

# pnpm
grep '@aws-sdk/client-secrets-manager' pnpm-lock.yaml
```

- If you see `"@aws-sdk/..."` entries, you're on v3 — match that version when installing `@aws-sdk/client-secrets-manager`.
- If you see `"aws-sdk"` (the v2 monolithic package), see the note below about upgrading before proceeding.

```sh
# npm
npm install --save-dev @aws-sdk/client-secrets-manager@^3.x

# yarn
yarn add --dev @aws-sdk/client-secrets-manager@^3.x

# pnpm
pnpm add --save-dev @aws-sdk/client-secrets-manager@^3.x
```

> **Note**: If your project is still on AWS SDK v2 (`aws-sdk` package), you will not be able to use `@theorchard/backend-secrets-manager-sdk-v3`. Upgrade to AWS SDK v3 first.

If your service is a GraphQL service or subgraph, also install `@theorchard/graphql-integration`:

```sh
# npm
npm install --save-dev @theorchard/graphql-integration@^2.2.0

# yarn
yarn add --dev @theorchard/graphql-integration@^2.2.0

# pnpm
pnpm add --save-dev @theorchard/graphql-integration@^2.2.0
```

## Configure docker-compose.yml

In `docker-compose.yml`, find the `integration-test` service and ensure it includes the following under `environment`:

```yaml
integration-test:
  environment:
    - AWS_REGION
    - AWS_ACCESS_KEY_ID
    - AWS_SECRET_ACCESS_KEY
    - AWS_SESSION_TOKEN
```

These are written without a value, telling Docker Compose to pass them through from the host where Jenkins' `withAWS` step will have injected them.

Do not add AWS credentials to `.env.shadow` — they are ephemeral session credentials provided by `withAWS` at CI runtime and should never be committed.

### Watch out for mock AWS credentials in shared test setup

Some projects set mock AWS credentials in a shared test setup file to prevent the AWS SDK from complaining during unit tests:

```js
// e.g. tests/setup.js, referenced via setupFiles in jest.config.js or vitest.config.mts
process.env.AWS_ACCESS_KEY_ID = 'test';
process.env.AWS_SECRET_ACCESS_KEY = 'test';
process.env.AWS_SESSION_TOKEN = 'test';
```

If this setup file runs for all tests (unit and integration alike), integration tests that call Secrets Manager will silently use the fake credentials and fail to retrieve any secrets. Scope it to unit tests only — for example by using a separate `jest.config.js` or `vitest.config.mts` for integration tests that does not include that setup file.

Assignments scoped to a single test file are fine and do not need to change.

## Sharing tokens across test suites (global setup)

Re-authenticating per test suite is slow when multiple suites share the same test users. Fetch all tokens once before any tests run and make them available as globals.

### Vitest

Vitest's `globalSetup` supports a `provide`/`inject` mechanism for passing values to test workers.

**`vitest.integration.global-setup.mts`** — fetch all tokens once before any tests run:

```ts
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { SecretsManager } from '@theorchard/backend-secrets-manager-sdk-v3';
import { loginFromSecretsManager } from '@theorchard/backend-jwtauth-testing';
import {
    SERVICE_NAME,
    MY_NEW_USER_CREDENTIALS_SECRET,
    MY_NEW_AUTH0_CREDENTIALS_SECRET,
} from './tests/constants';

export default async function setup({ provide }: { provide: (key: string, value: unknown) => void }) {
    const secretsManager = new SecretsManager({
        secretsManagerClient: new SecretsManagerClient(),
    });

    // when fetching multiple users, wrap loginFromSecretsManager calls in Promise.all
    const myNewUserJwt = await loginFromSecretsManager(
        { service_name: SERVICE_NAME, secret_name: MY_NEW_USER_CREDENTIALS_SECRET, environment: 'qa' },
        { service_name: SERVICE_NAME, secret_name: MY_NEW_AUTH0_CREDENTIALS_SECRET, environment: 'qa' },
        secretsManager,
    );

    provide('MY_SERVICE_MY_NEW_USER_JWT', myNewUserJwt);
}
```

**`vitest.setup.mts`** — inject tokens into global scope:

```ts
import { inject } from 'vitest';

declare module 'vitest' {
    interface ProvidedContext {
        MY_SERVICE_MY_NEW_USER_JWT: string;
    }
}

declare global {
    var MY_SERVICE_MY_NEW_USER_JWT: string;
}

try {
    const myNewUserJwt = inject('MY_SERVICE_MY_NEW_USER_JWT');
    if (myNewUserJwt) {
        global.MY_SERVICE_MY_NEW_USER_JWT = myNewUserJwt;
    }
} catch {
    // Running unit tests — tokens not available
    global.MY_SERVICE_MY_NEW_USER_JWT = '';
}
```

**`vitest.config.mts`** — wire up `globalSetup` to the integration project only:

```ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
    test: {
        globals: true,
        setupFiles: ['./vitest.setup.mts'],
        projects: [
            {
                extends: true,
                test: {
                    name: 'unit',
                    include: ['./src/**/*.{test,spec}.{ts,mts}'],
                },
            },
            {
                extends: true,
                test: {
                    name: 'integration',
                    globalSetup: './vitest.integration.global-setup.mts',
                    include: ['./tests/**/*.{test,spec}.{ts,mts}'],
                },
            },
        ],
    },
});
```

### Jest

Jest's `globalSetup` runs in a separate process from test workers, so values are passed via `process.env`. A `setupFiles` entry then copies them onto `global`.

**`jest.integration.globalSetup.ts`**:

```ts
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { SecretsManager } from '@theorchard/backend-secrets-manager-sdk-v3';
import { loginFromSecretsManager } from '@theorchard/backend-jwtauth-testing';
import {
    SERVICE_NAME,
    MY_NEW_USER_CREDENTIALS_SECRET,
    MY_NEW_AUTH0_CREDENTIALS_SECRET,
} from './tests/constants';

export default async function globalSetup() {
    const secretsManager = new SecretsManager({
        secretsManagerClient: new SecretsManagerClient(),
    });

    // when fetching multiple users, wrap loginFromSecretsManager calls in Promise.all
    const myNewUserJwt = await loginFromSecretsManager(
        { service_name: SERVICE_NAME, secret_name: MY_NEW_USER_CREDENTIALS_SECRET, environment: 'qa' },
        { service_name: SERVICE_NAME, secret_name: MY_NEW_AUTH0_CREDENTIALS_SECRET, environment: 'qa' },
        secretsManager,
    );

    process.env.MY_SERVICE_MY_NEW_USER_JWT = myNewUserJwt;
}
```

**`jest.integration.setup.ts`** — copy env vars onto global:

```ts
declare global {
    var MY_SERVICE_MY_NEW_USER_JWT: string;
}

global.MY_SERVICE_MY_NEW_USER_JWT = process.env.MY_SERVICE_MY_NEW_USER_JWT ?? '';
```

**`jest.integration.config.ts`**:

```ts
import type { Config } from 'jest';

const config: Config = {
    globalSetup: './jest.integration.globalSetup.ts',
    setupFiles: ['./jest.integration.setup.ts'],
    testMatch: ['<rootDir>/tests/**/*.{test,spec}.{ts,js}'],
    testEnvironment: 'node',
};

export default config;
```

> **Note**: `setupFiles` runs before the test framework is installed in each worker, which is fine for assigning `global` from `process.env`. If you need Jest globals like `beforeAll` or `expect` inside the setup file, switch `setupFiles` to `setupFilesAfterFramework`, which runs after the test framework is installed.

## Add constants

In `tests/integration/constants.js`, export the new user's details. If `SERVICE_NAME` doesn't already exist in this file, add it — it must match the `service_name` variable in `variables.tf` (typically `<service>-integration-test`):

```js
// Must match service_name in variables.tf — determines the Secrets Manager path
export const SERVICE_NAME = 'my-service-integration-test';

// Secret name suffixes — must match what you added to terraform variables.tf
export const MY_NEW_USER_CREDENTIALS_SECRET = 'MY_NEW_USER_CREDENTIALS';
export const MY_NEW_AUTH0_CREDENTIALS_SECRET = 'MY_NEW_AUTH0_CREDENTIALS';

// The identity UUID of this user in the system under test
export const MY_NEW_TEST_USER_IDENTITY_ID = '<uuid-from-ows-users>';

// If the service requires OWS request context headers, add a BOGUS_CONTEXT.
// (Some services gate on these even for flows that shouldn't need them —
//  check the service docs or ask the team.)
export const MY_NEW_TEST_USER_BOGUS_CONTEXT = {
    'Orchard-Identity-Id': MY_NEW_TEST_USER_IDENTITY_ID,
    'Orchard-Profile-Type': 'SettingsProfile',
    'Orchard-Profile-Id': 123456,
};
```

## Use the token in a test

The globals set by global setup are session-scoped fixtures — fetched once and consumed across all test suites. Read the token from the global at the top of each suite:

```ts
import { MY_NEW_TEST_USER_BOGUS_CONTEXT } from '../constants';

describe('My new scenario', () => {
    const token = global.MY_SERVICE_MY_NEW_USER_JWT;

    it('should do something', async () => {
        const response = await fetch('/some-endpoint', {
            headers: {
                ...MY_NEW_TEST_USER_BOGUS_CONTEXT,
                Authorization: `Bearer ${token}`,
            },
        });
        expect(response.status).toBe(200);
    });
});
```

For GraphQL services, use `GraphQLClient` from `@theorchard/graphql-integration`:

```ts
import { GraphQLClient } from '@theorchard/graphql-integration';
import { MY_NEW_TEST_USER_BOGUS_CONTEXT, QUERIES_PATH } from '../constants';

describe('My new scenario', () => {
    const token = global.MY_SERVICE_MY_NEW_USER_JWT;
    const graphQLClient = new GraphQLClient(QUERIES_PATH);
    let result;

    beforeAll(async () => {
        const response = await graphQLClient.executeQuery<MyQuery>({
            query,
            headers: {
                ...MY_NEW_TEST_USER_BOGUS_CONTEXT,
                authorization: `Bearer ${token}`,
            },
            variables: { /* ... */ },
        });
        result = response.someField;
    });

    test('expected behavior', () => {
        expect(result).toBeDefined();
    });
});
```

## Key details

- `loginFromSecretsManager` fetches both the user credentials and Auth0 credentials from Secrets Manager and returns a bearer token in one call — don't call the lower-level `generateBearerJwtToken` functions directly.
- `service_name` in each lookup must match the `service_name` variable in `variables.tf` (typically `<service>-integration-test`), since that determines the Secrets Manager path `qa/<service_name>/<secret_name>`.
- `SecretsManagerClient` picks up `AWS_REGION` and credentials from environment variables automatically — no need to pass them explicitly.
- MFA is handled transparently if `otp_secret_key` is present in the user credentials secret.
