first commit
This commit is contained in:
20
docs/networking/network/network-testing/README.md
Normal file
20
docs/networking/network/network-testing/README.md
Normal 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 -->
|
||||
|
||||
@@ -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.
|
||||
56
docs/networking/network/network-testing/src/error-codes.md
Normal file
56
docs/networking/network/network-testing/src/error-codes.md
Normal 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
71
docs/networking/network/network-testing/src/types/common.md
Normal file
71
docs/networking/network/network-testing/src/types/common.md
Normal 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.
|
||||
Reference in New Issue
Block a user