# Module Test Patterns

# Module Test Patterns

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/config/__tests__/config-loader.test.ts](src/config/__tests__/config-loader.test.ts)
- [src/env/__tests__/env-validator.test.ts](src/env/__tests__/env-validator.test.ts)
- [src/http/__tests__/http-server.test.ts](src/http/__tests__/http-server.test.ts)
- [src/lifecycle/__tests__/graceful-shutdown.test.ts](src/lifecycle/__tests__/graceful-shutdown.test.ts)
- [src/migrations/__tests__/apply-patches.test.ts](src/migrations/__tests__/apply-patches.test.ts)
- [src/nats/__tests__/nats-client.test.ts](src/nats/__tests__/nats-client.test.ts)
- [src/service/__tests__/service-registrar.test.ts](src/service/__tests__/service-registrar.test.ts)

</details>



The `@primebrick/sdk` employs a testing strategy focused on isolation, portability, and high-fidelity simulation of external dependencies. By leveraging the hexagonal architecture (Ports & Adapters), the SDK ensures that core logic remains testable without requiring a live database or network connection, while integration-level tests verify real HTTP and process-level behaviors.

## Port Isolation via Mock Adapters

The primary pattern for testing business logic in the `Config`, `Service`, and `Migrations` modules is the use of in-memory mock adapters for their respective ports. This allows for precise control over the data returned to the SDK and the verification of side effects.

### Config & Service Repository Mocks
The `ConfigLoader` and `ServiceRegistrar` are tested using factory functions that generate mock implementations of `ConfigRepositoryPort` and `ServiceRegistryPort`.

*   **`makeRepo` (Config)**: Creates a mock that returns a predefined set of configuration rows [src/config/__tests__/config-loader.test.ts:5-9]().
*   **`makeRepo` (Service)**: Creates a mock using `vi.fn()` to track calls to `insert`, `updateByCode`, and `findByCode` [src/service/__tests__/service-registrar.test.ts:6-16]().

### Database Port Mock
The `Migrations` module requires a more sophisticated mock to simulate transaction blocks and query tracking. The `makeDb` utility captures every SQL statement and parameter sent to the adapter [src/migrations/__tests__/apply-patches.test.ts:10-23]().

**Data Flow: Mock Adapter Pattern**

| Component | Port Mock Implementation | Verification Strategy |
| :--- | :--- | :--- |
| **ConfigLoader** | Returns static array of `{ key, value }` [src/config/__tests__/config-loader.test.ts:15-19]() | Assert `getTyped` converts types correctly [src/config/__tests__/config-loader.test.ts:50-53]() |
| **ServiceRegistrar** | `vi.fn()` for CRUD operations [src/service/__tests__/service-registrar.test.ts:11-15]() | Assert `insert` vs `update` based on `findByCode` result [src/service/__tests__/service-registrar.test.ts:33-58]() |
| **Migrations** | Tracks SQL strings in a `calls` array [src/migrations/__tests__/apply-patches.test.ts:13-17]() | Verify `BEGIN` and `COMMIT` wrap the DDL execution [src/migrations/__tests__/apply-patches.test.ts:57-61]() |

Sources: [src/config/__tests__/config-loader.test.ts:5-20](), [src/service/__tests__/service-registrar.test.ts:6-31](), [src/migrations/__tests__/apply-patches.test.ts:10-23]()

## Filesystem & Environment Fixtures

Modules that interact with the environment or the local disk use temporary fixtures and process state management to ensure test idempotency.

### Temporary Patch Directories
To test `applyPatches`, the test suite creates a unique temporary directory for each test case using `mkdtempSync` [src/migrations/__tests__/apply-patches.test.ts:28-30](). This directory is populated with `.sql` files to simulate different migration scenarios (new patches, duplicate SHAs, or modified content) and is recursively deleted after each test [src/migrations/__tests__/apply-patches.test.ts:31-33]().

### Environment Isolation
The `env` module tests preserve the original `process.env` by cloning it before each test and restoring it afterward [src/env/__tests__/env-validator.test.ts:5-13](). This prevents cross-test contamination when validating required variables or default values.

**Code Entity Space: Migration Test Setup**
```mermaid
graph TD
    subgraph "Test Execution"
        A["apply-patches.test.ts"] --> B["mkdtempSync"]
        B --> C["Temporary Directory"]
        C --> D["writeFileSync(0001_init.sql)"]
    end

    subgraph "SDK Logic"
        E["applyPatches()"] --> F["fs.readdirSync"]
        F --> C
        E --> G["DatabasePort.query()"]
    end

    G --> H["makeDb Mock"]
    H -- "Capture SQL" --> I["calls: Array"]
```
Sources: [src/migrations/__tests__/apply-patches.test.ts:25-61](), [src/env/__tests__/env-validator.test.ts:1-13]()

## Singleton & Lifecycle Patterns

Testing global state and process-level events requires specialized mocking of Node.js internals.

### NatsClient Singleton Reset
The `NatsClient` is a singleton. To test its lazy-connection logic, the test suite invokes a private-access style `close()` method in `beforeEach` to nullify the internal connection reference [src/nats/__tests__/nats-client.test.ts:16-20](). The `nats` module itself is mocked using `vi.mock` to return a fake connection object [src/nats/__tests__/nats-client.test.ts:4-10]().

### GracefulShutdown Process Mocking
Testing `GracefulShutdown` requires preventing the test runner from actually exiting.
1.  **`process.exit`**: Spied on and mocked to throw a specific `__EXIT__` error, which is then caught by the test's expectation [src/lifecycle/__tests__/graceful-shutdown.test.ts:9-11]().
2.  **Signal Handlers**: `process.on` is spied on to verify that `SIGTERM`, `SIGINT`, and `uncaughtException` handlers are correctly registered during `install()` [src/lifecycle/__tests__/graceful-shutdown.test.ts:47-56]().

Sources: [src/nats/__tests__/nats-client.test.ts:1-20](), [src/lifecycle/__tests__/graceful-shutdown.test.ts:4-57]()

## Integration: Real HTTP Binding

Unlike other modules, the `HttpServer` tests perform a "black-box" integration test by binding to a real network port.

### Dynamic Port Assignment
To avoid port conflicts during parallel test execution, the server is configured to bind to port `0` [src/http/__tests__/http-server.test.ts:19](). The OS assigns a random free port, which is then retrieved via `server.address()` for use in test requests [src/http/__tests__/http-server.test.ts:23-24]().

### HealthCheck Verification
The `HttpServer` test wires a real `HealthCheck` instance to a mocked `HealthCheckPort` [src/http/__tests__/http-server.test.ts:20-21](). This allows testing the full HTTP lifecycle:
1.  Mock `HealthCheckPort.ping` returns `true`.
2.  Test performs `fetch()` against `http://127.0.0.1:{port}/health` [src/http/__tests__/http-server.test.ts:32]().
3.  Asserts `200 OK` and JSON body `status: "healthy"` [src/http/__tests__/http-server.test.ts:33-36]().

**Code Entity Space: HTTP Test Lifecycle**
```mermaid
sequenceDiagram
    participant T as "http-server.test.ts"
    participant S as "HttpServer (http.ts)"
    participant H as "HealthCheck (health.ts)"
    participant M as "HealthCheckPort (Mock)"

    T->>S: createHttpServer({ port: 0 })
    S-->>T: Server Instance (Port: 49152)
    T->>T: fetch("http://localhost:49152/health")
    T->>S: HTTP GET /health
    S->>H: runAll()
    H->>M: ping()
    M-->>H: true
    H-->>S: { status: "healthy" }
    S-->>T: 200 OK { "status": "healthy" }
```
Sources: [src/http/__tests__/http-server.test.ts:13-43](), [src/http/__tests__/http-server.test.ts:67-101]()

---
