first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# @suaveplan/network-testing - Network testing utilities: in-memory transport, fake WebSocket adapter, condition simulator, test peer factory, and event capture assertions
<!-- begin:auto-docs -->
## Documentation tree
- [event-capture](./src/assertions/event-capture/event-capture.md) — The `event-capture` module provides a testing utility that records every event emitted through an `EventBusLike` instan…
- [event-capture](./src/assertions/event-capture/event-capture.md) — The `event-capture` module provides a testing utility that records every event emitted through an `EventBusLike` instan…
- [test-peer](./src/factories/test-peer/test-peer.md) — `test-peer` provides a fluent factory for generating synthetic peer identities during network tests. Instead of mocking…
- [test-peer](./src/factories/test-peer/test-peer.md) — `test-peer` provides a fluent factory for generating synthetic peer identities during network tests. Instead of mocking…
- [condition-simulator](./src/simulators/condition-simulator/condition-simulator.md) — `condition-simulator` wraps any transport-like object and injects real-world network impairments: latency, jitter, pack…
- [condition-simulator](./src/simulators/condition-simulator/condition-simulator.md) — `condition-simulator` wraps any transport-like object and injects real-world network impairments: latency, jitter, pack…
- [fake-websocket](./src/transports/fake-websocket/fake-websocket.md) — This module provides an in-memory WebSocket replacement for tests that need to verify transport-layer behaviour without…
- [fake-websocket](./src/transports/fake-websocket/fake-websocket.md) — This module provides an in-memory WebSocket replacement for tests that need to verify transport-layer behaviour without…
- [memory-transport](./src/transports/memory-transport/memory-transport.md) — `memory-transport` creates a pair of connected in-memory transport endpoints. It is the fastest way to write unit tests…
- [memory-transport](./src/transports/memory-transport/memory-transport.md) — `memory-transport` creates a pair of connected in-memory transport endpoints. It is the fastest way to write unit tests…
- [types](./src/types/common.md) — This module declares the public type surface of `@suaveplan/network-testing`. All interfaces are pure types with no run…
- [common](./src/types/common.md) — This module declares the public type surface of `@suaveplan/network-testing`. All interfaces are pure types with no run…
- [error-codes](./src/error-codes.md) — This module defines the canonical error taxonomy for the `@suaveplan/network-testing` package. All test-failure conditi…
<!-- end:auto-docs -->

View File

@@ -0,0 +1,58 @@
# Event Capture
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The `event-capture` module provides a testing utility that records every event
emitted through an `EventBusLike` instance. It is designed for network test
suites that need to assert on side-effects, message delivery, or lifecycle
transitions without coupling tests to internal state.
## Features
- Transparent interception of all `emit` calls on the wrapped bus.
- Polling `waitFor` with configurable timeout for asynchronous assertions.
- Strict `assertEventEmitted` and `assertEventCount` helpers that throw
`NetworkTestError` on mismatch, producing deterministic test failures.
- `clear()` to reset captured history between test phases.
## Usage
```ts
import { EventBus } from "@suaveplan/events";
import { createNetworkEventCapture } from "@suaveplan/network-testing/assertions/events";
const bus = new EventBus<{ peer: { id: string } }>();
const capture = createNetworkEventCapture(bus);
bus.emit("peer", { id: "p1" });
capture.assertEventEmitted("peer");
capture.assertEventCount("peer", 1);
```
## API Reference
### `createNetworkEventCapture(bus)`
Returns a `NetworkEventCapture` that wraps the given `EventBusLike`.
### `NetworkEventCapture`
- `events` — read-only array of `{ type, data }` objects.
- `waitFor(event, timeout?)` — resolves with the first matching payload or
rejects with `NetworkTestError` (`NET_TEST_TIMEOUT`).
- `assertEventEmitted(event, predicate?)` — throws `NetworkTestError`
(`NET_TEST_EVENT_NOT_EMITTED`) if no matching event was captured.
- `assertEventCount(event, count)` — throws if the captured count differs.
- `clear()` — empties the `events` array.
## Implementation Notes
The implementation mutates the `emit` method on the wrapped bus via a proxy
assignment. The original bound function is stored so that event dispatch
continues normally. Because `EventBusLike` is generic, the wrapper uses
`unknown` casts to avoid index-signature type errors. This is safe because
`string` and `unknown` are supertypes of all possible event keys and payloads.

View File

@@ -0,0 +1,56 @@
# Error Codes
## Purpose
This module defines the canonical error taxonomy for the `@suaveplan/network-testing`
package. All test-failure conditions are represented as typed `NetworkTestError`
instances so that consumers can distinguish between assertion failures,
timeouts, and transport-level problems without parsing strings.
## Features
- `NETWORK_TEST_CODES` registry with three standard codes: `TIMEOUT`,
`EVENT_NOT_EMITTED`, and `CONNECT_FAILED`.
- `NetworkTestError` class extending `SuaveplanError<NetworkTestErrorCode>`.
- Zero-constructor subclass pattern to satisfy the `protected` base constructor.
- Strongly typed error codes that integrate with the project-wide error
registry and telemetry systems.
- Predictable error identifiers that remain stable across refactors.
## Usage
```ts
import { NetworkTestError, NETWORK_TEST_CODES } from "@suaveplan/network-testing";
throw new NetworkTestError(
NETWORK_TEST_CODES.TIMEOUT,
"test.timeout",
{ context: { event: "peer.join", timeout: 1000 } }
);
```
## API Reference
### `NETWORK_TEST_CODES`
An object literal mapping short names to fully-qualified code strings such as
`NET_TEST_TIMEOUT`.
### `NetworkTestErrorCode`
A union of all values in `NETWORK_TEST_CODES`.
### `NetworkTestError`
Extends `SuaveplanError`. No custom constructor is declared; callers use the
base constructor directly, passing a code, translation key, and optional
metadata.
## Implementation Notes
The subclass does not define its own constructor. This is intentional:
`SuaveplanError` declares a `protected` constructor, and TypeScript allows
subclasses to inherit it directly as long as they do not try to declare a
narrower or conflicting signature. This keeps the error hierarchy shallow and
avoids boilerplate. Error codes follow the standard `NET_TEST_` prefix so they
can be searched and filtered easily in telemetry dashboards and log aggregators.

View File

@@ -0,0 +1,60 @@
# Test Peer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`test-peer` provides a fluent factory for generating synthetic peer identities
during network tests. Instead of mocking raw peer data inline, tests can create
`TestPeer` objects that expose registry entries, bootstrap seeds, and health
metadata in a single place.
## Features
- Auto-generated `id` and `address` with collision-safe counters.
- Fluent `withCapabilities`, `withRegion`, and `withHealth` methods that return
immutable copies, preserving the original peer.
- Direct conversion to registry entry (`asRegistryEntry`) and bootstrap seed
(`asBootstrapSeed`) formats.
## Usage
```ts
import { createTestPeer } from "@suaveplan/network-testing/factories/peer";
const peer = createTestPeer()
.withCapabilities(["relay", "sign"])
.withRegion("us-east")
.withHealth(true, 20);
const seed = peer.asBootstrapSeed();
```
## API Reference
### `createTestPeer(options?)`
Creates a `TestPeer`. Optional overrides: `id`, `address`, `capabilities`,
`region`.
### `TestPeer`
- `id` — unique peer identifier.
- `address` — host:port string.
- `capabilities` — read-only capability list.
- `region` — geographic or logical region label.
- `asRegistryEntry()` — returns `{ peerId, address, tier, latencyMs }` where
`tier` is `1` for healthy peers and `3` for unhealthy peers.
- `asBootstrapSeed()` — returns `{ peerId, address, capabilities, region }`.
- `withCapabilities(capabilities)` — returns a new peer with merged capabilities.
- `withRegion(region)` — returns a new peer in the given region.
- `withHealth(healthy, latencyMs?)` — returns a new peer with updated health.
## Implementation Notes
The fluent API creates a new `TestPeerImpl` on every mutation rather than
mutating internal state. This makes assertions in multi-step tests deterministic
and prevents accidental shared-state bugs when the same base peer is reused
across multiple test cases.

View File

@@ -0,0 +1,64 @@
# Condition Simulator
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`condition-simulator` wraps any transport-like object and injects real-world
network impairments: latency, jitter, packet loss, bandwidth limits, and forced
disconnections. It lets unit tests exercise retry, timeout, and circuit-breaker
logic without depending on actual network infrastructure.
## Features
- **Packet loss** — drops a configurable fraction of sends.
- **Latency & jitter** — delays messages by a constant or sampled distribution.
- **Bandwidth shaping** — adds extra delay proportional to payload size.
- **Forced disconnect** — throws after cumulative latency exceeds a threshold.
- **Runtime tuning** — `setConditions` and `reset` allow mid-test scenario changes.
- **Injectable RNG** — supports deterministic replay when a seeded `Rng` is
supplied.
## Usage
```ts
import { createConditionSimulator } from "@suaveplan/network-testing/simulators/condition";
const sim = createConditionSimulator({ latencyMs: 100, packetLossRate: 0.1 });
const wrapped = sim.wrap(transport);
await wrapped.send("hello");
console.log(sim.stats.avgLatencyMs);
```
## API Reference
### `createConditionSimulator(conditions, rng?)`
Returns a `ConditionSimulator`.
### `ConditionSimulator`
- `wrap(transport)` — returns a proxied transport whose `send` is intercepted.
- `setConditions(partial)` — merges new values into the active condition set.
- `reset()` — restores original conditions and clears statistics.
- `stats` — read-only `{ sentMessages, droppedMessages, totalLatencyMs,
avgLatencyMs }`.
### `NetworkConditions`
- `latencyMs` — constant delay or a `LatencyDistribution` object.
- `jitterMs` — random ±offset applied after latency.
- `packetLossRate` — probability between `0` and `1`.
- `bandwidthBps` — bytes-per-second cap.
- `disconnectAfterMs` — cumulative latency threshold that triggers a simulated
disconnect.
## Implementation Notes
The wrapper uses a `Proxy` to intercept only the `send` property, leaving all
other transport methods untouched. Latency sampling supports uniform, normal
(Box-Muller), and bimodal distributions. Statistics are updated synchronously
before the delay so that `stats.sentMessages` reflects the total attempted sends
immediately.

View File

@@ -0,0 +1,63 @@
# Fake WebSocket Adapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module provides an in-memory WebSocket replacement for tests that need to
verify transport-layer behaviour without spinning up a real WebSocket server.
It models connection lifecycle, message send/receive, error injection, and close
semantics with enough fidelity to test protocol plugins and reconnection logic.
## Features
- `FakeWebSocketHandle` with full `readyState` lifecycle (`CONNECTING`, `OPEN`,
`CLOSING`, `CLOSED`).
- `send` tracking on each handle.
- `simulateMessage`, `simulateError`, and `simulateClose` broadcast to every
open handle.
- Optional `autoConnect` flag for testing manual connection flows.
- Multiple concurrent connections per adapter.
## Usage
```ts
import { createFakeWebSocketAdapter } from "@suaveplan/network-testing/transports/websocket";
const adapter = createFakeWebSocketAdapter();
const handle = adapter.connect("ws://relay/1");
handle.onmessage = (data) => console.log(data);
adapter.simulateMessage("hello");
```
## API Reference
### `createFakeWebSocketAdapter(options?)`
Creates a `FakeWebSocketAdapter`. Options: `autoConnect` (default `true`).
### `FakeWebSocketAdapter`
- `connect(url, protocols?)` — creates a `FakeWebSocketHandle`.
- `simulateMessage(data)` — delivers the message to every open handle.
- `simulateError(error)` — fires `onerror` on every handle.
- `simulateClose(code?, reason?)` — transitions handles to `CLOSED`.
- `connections` — read-only array of `{ url, handle }` objects.
### `FakeWebSocketHandle`
- `readyState` — `0` (`CONNECTING`), `1` (`OPEN`), `2` (`CLOSING`), `3` (`CLOSED`).
- `sentMessages` — array of payloads passed to `send`.
- `send(data)` — throws if not open.
- `close(code?, reason?)` — transitions to closed and fires `onclose`.
- `onopen`, `onclose`, `onmessage`, `onerror` — standard callback slots.
## Implementation Notes
Handles are stored in an internal array and broadcast methods iterate over the
entire collection. This is acceptable because test adapters typically manage
fewer than a dozen handles. `readyState` transitions skip `CLOSING` for
simplicity; `simulateClose` moves directly from `OPEN` to `CLOSED`.

View File

@@ -0,0 +1,64 @@
# Memory Transport
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`memory-transport` creates a pair of connected in-memory transport endpoints.
It is the fastest way to write unit tests for network plugins, message routers,
and topology managers because it bypasses real I/O entirely while preserving the
`send`/`connect`/`disconnect` contract.
## Features
- Bidirectional message queue shared between `client` and `server`.
- Configurable artificial latency via `latencyMs`.
- AbortSignal support for cancellation tests.
- Event hooks (`connect`, `disconnect`, `message`) with unsubscribe.
- State machine (`connecting` → `connected` → `disconnected`).
## Usage
```ts
import { createMemoryTransport } from "@suaveplan/network-testing/transports/memory";
const pair = createMemoryTransport({ latencyMs: 10 });
pair.connect();
pair.server.on("message", (msg) => console.log(msg));
await pair.client.send("hello");
```
## API Reference
### `createMemoryTransport(options?)`
Returns a `MemoryTransportPair`. Options: `latencyMs`, `clock`.
### `MemoryTransportPair`
- `client` — client-side `MemoryTransport`.
- `server` — server-side `MemoryTransport`.
- `connect()` — transitions both ends to `connected` and fires handlers.
- `disconnect()` — transitions both ends to `disconnected` and fires handlers.
### `MemoryTransport`
- `id` — unique transport identifier.
- `state` — current `TransportState`.
- `messages` — array of delivered payloads.
- `send(message, signal?)` — enqueues on the peer after optional latency.
- `connect(signal?)` — transitions to `connected`.
- `disconnect()` — transitions to `disconnected`.
- `on(event, handler)` — subscribes; returns an unsubscribe function.
## Implementation Notes
The pair uses a cross-reference pattern: each `MemoryTransportImpl` holds a
reference to the peer via a shared mutable object, avoiding circular constructor
dependencies. Latency is implemented with `setTimeout`; when `latencyMs` is zero,
messages are delivered synchronously. The `send` method validates state before
enqueuing and throws a plain `Error` with a descriptive message if the transport
is not connected.

View File

@@ -0,0 +1,71 @@
# Common Types
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module declares the public type surface of `@suaveplan/network-testing`.
All interfaces are pure types with no runtime footprint, allowing consumers to
import them without increasing bundle size.
## Features
- Transport abstractions (`MemoryTransport`, `MemoryTransportPair`).
- WebSocket testing types (`FakeWebSocketAdapter`, `FakeWebSocketHandle`).
- Network condition modelling (`NetworkConditions`, `LatencyDistribution`).
- Peer factory types (`TestPeer`, `TestPeerOptions`, `SeedConfig`).
- Event capture contract (`NetworkEventCapture`).
## Usage
```ts
import type { MemoryTransportPair, NetworkConditions } from "@suaveplan/network-testing";
function testWithLatency(pair: MemoryTransportPair, cond: NetworkConditions) {
// ...
}
```
## API Reference
### Transport Types
- `TransportState` — `"connecting" | "connected" | "disconnected"`.
- `MemoryTransportOptions` — `{ latencyMs?, clock? }`.
- `MemoryTransport` — single endpoint with `send`, `connect`, `disconnect`, `on`.
- `MemoryTransportPair` — `{ client, server, connect(), disconnect() }`.
### WebSocket Types
- `FakeWebSocketOptions` — `{ autoConnect?, clock? }`.
- `FakeWebSocketHandle` — standard WebSocket-like handle with callbacks.
- `FakeWebSocketAdapter` — factory and simulation surface.
### Simulation Types
- `LatencyDistribution` — `{ min, max, distribution: "uniform" | "normal" | "bimodal" }`.
- `NetworkConditions` — optional latency, jitter, packet loss, bandwidth, and
disconnect threshold.
- `SimulationStats` — counters and latency averages.
- `ConditionSimulator` — `wrap`, `setConditions`, `reset`, and `stats`.
### Peer Types
- `TestPeerOptions` — overrides for id, address, capabilities, region, clock.
- `SeedConfig` — bootstrap seed shape.
- `TestPeer` — fluent peer builder.
### Event Types
- `NetworkEventCapture` — `events`, `waitFor`, `assertEventEmitted`,
`assertEventCount`, `clear`.
## Implementation Notes
Types are intentionally minimal and focused exclusively on testing concerns.
Domain-specific network concepts such as `PeerId` and `MessageEnvelope` live in
`@suaveplan/network` rather than here, ensuring that this package remains a
clean testing utility and does not become a secondary source of network
contracts that would drift out of sync with the core implementation.