# NATS.E2E.Tests — Implementation Plan

**Date:** 2026-03-12
**Design:** [2026-03-12-e2e-tests-design.md](2026-03-12-e2e-tests-design.md)

## Steps

### Step 1: Create the project and add to solution

**Files:**
- `tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj` (new)
- `NatsDotNet.slnx` (edit)

**Details:**

Create `tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj`:
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="coverlet.collector" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" />
    <PackageReference Include="NATS.Client.Core" />
    <PackageReference Include="Shouldly" />
    <PackageReference Include="xunit" />
    <PackageReference Include="xunit.runner.visualstudio" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Xunit" />
    <Using Include="Shouldly" />
  </ItemGroup>

</Project>
```

No project references — black-box only. All package versions come from `Directory.Packages.props` (CPM). TFM inherited from `Directory.Build.props` (`net10.0`).

Add to `NatsDotNet.slnx` under the `/tests/` folder:
```xml
<Project Path="tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj" />
```

**Verify:** `dotnet build tests/NATS.E2E.Tests` succeeds.

---

### Step 2: Implement `NatsServerProcess`

**Files:**
- `tests/NATS.E2E.Tests/Infrastructure/NatsServerProcess.cs` (new)

**Details:**

Class `NatsServerProcess : IAsyncDisposable`:

- **Constructor**: Takes no args. Allocates a free TCP port via ephemeral socket bind.
- **`StartAsync()`**:
  1. Resolves the host DLL path: walk up from `AppContext.BaseDirectory` to find the solution root (contains `NatsDotNet.slnx`), then build path `src/NATS.Server.Host/bin/Debug/net10.0/NATS.Server.Host.dll`. If not found, run `dotnet build src/NATS.Server.Host/NATS.Server.Host.csproj -c Debug` from solution root first.
  2. Launch `dotnet exec <dll> -p <port>` via `System.Diagnostics.Process`. Redirect stdout/stderr, capture into `StringBuilder` for diagnostics.
  3. Poll TCP connect to `127.0.0.1:<port>` every 100ms, timeout after 10s. Throw `TimeoutException` with captured output if server doesn't become ready.
- **`DisposeAsync()`**:
  1. If process is running: try `Process.Kill(entireProcessTree: true)` (cross-platform in .NET 10).
  2. Wait for exit with 5s timeout, then force kill if still alive.
  3. Dispose the process.
- **Properties**: `int Port`, `string Output` (captured stdout+stderr for diagnostics).

**Verify:** Builds without errors.

---

### Step 3: Implement `NatsServerFixture`

**Files:**
- `tests/NATS.E2E.Tests/Infrastructure/NatsServerFixture.cs` (new)

**Details:**

Class `NatsServerFixture : IAsyncLifetime`:

- **Field**: `NatsServerProcess _server`
- **`InitializeAsync()`**: Create `NatsServerProcess`, call `StartAsync()`.
- **`DisposeAsync()`**: Dispose the server process.
- **`int Port`**: Delegates to `_server.Port`.
- **`NatsConnection CreateClient()`**: Returns `new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{Port}" })`.

Define a collection attribute:
```csharp
[CollectionDefinition("E2E")]
public class E2ECollection : ICollectionFixture<NatsServerFixture>;
```

This lets multiple test classes share one server process via `[Collection("E2E")]`.

**Verify:** Builds without errors.

---

### Step 4: Implement `BasicTests`

**Files:**
- `tests/NATS.E2E.Tests/BasicTests.cs` (new)

**Details:**

```csharp
[Collection("E2E")]
public class BasicTests(NatsServerFixture fixture)
{
    private static CancellationToken Timeout => new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
```

**Test 1 — `ConnectAndPing`:**
- Create client via `fixture.CreateClient()`
- `await client.ConnectAsync()`
- `await client.PingAsync()` — if no exception, PING/PONG succeeded
- Assert `client.ConnectionState` is `Open` (via Shouldly)

**Test 2 — `PubSub`:**
- Create two clients (pub, sub)
- Connect both
- Subscribe sub to `"e2e.test.pubsub"`
- Flush sub via `PingAsync()`
- Publish `"hello e2e"` on the subject
- Read one message from subscription with 10s timeout
- Assert `msg.Data.ShouldBe("hello e2e")`

**Test 3 — `RequestReply`:**
- Create two clients (requester, responder)
- Connect both
- Subscribe responder to `"e2e.test.rpc"`, in a background task read messages and reply with `"reply: " + msg.Data`
- Flush responder via `PingAsync()`
- Send request from requester: `await requester.RequestAsync<string, string>("e2e.test.rpc", "ping")`
- Assert reply data is `"reply: ping"`

All tests use `await using` for client cleanup.

**Verify:** `dotnet test tests/NATS.E2E.Tests` — all 3 tests pass.

---

### Step 5: Verify full solution builds and tests pass

**Commands:**
```bash
dotnet build
dotnet test tests/NATS.E2E.Tests -v normal
```

**Success criteria:** Solution builds clean, all 3 E2E tests pass.

## Batch Structure

All 5 steps are in a single batch — the project is small and sequential (each step builds on the prior). No parallelization needed.

| Step | Files | Depends On |
|------|-------|------------|
| 1 | csproj + slnx | — |
| 2 | NatsServerProcess.cs | Step 1 |
| 3 | NatsServerFixture.cs | Step 2 |
| 4 | BasicTests.cs | Step 3 |
| 5 | (verify only) | Step 4 |
