/**
 * In-process test helpers for WebSocket JSON-RPC round-trips.
 *
 * Drives `register_action_ws` without an HTTP server. Consumers supply
 * specs + handlers; the harness constructs real `WSContext` instances
 * backed by test-owned `send`/`close` pairs, fakes the authenticated
 * Hono context (`request_context`, credential type, session id, api
 * token id), and exposes a `connect()` factory returning a `WsClient`
 * per connection.
 *
 * Three layers are exported:
 *
 *   - **Primitives** (`create_fake_ws`, `create_fake_hono_context`,
 *     `create_stub_upgrade`, `MinimalActionEnvironment`,
 *     `dispatch_ws_message`) — used by fuz_app's own dispatcher tests
 *     and by consumers wiring tight one-off tests.
 *   - **Harness** (`create_ws_test_harness`, `keeper_identity`) — the
 *     high-level driver. Give it specs + handlers, get back
 *     `{transport, connect()}`. `connect()` is async and resolves after
 *     `on_socket_open` completes, so broadcasts sent immediately after
 *     `await harness.connect()` reach the client. Returns a `WsClient`
 *     (shared interface — see `transports/ws_client.ts`); the same
 *     interface is implemented by `transports/ws_transport.ts` for
 *     cross-process tests.
 *   - **Broadcast wiring** — `build_broadcast_api` for wiring a typed
 *     broadcast API against the harness's transport. Wire-frame types
 *     + predicates (`is_notification`, `is_response_for`,
 *     `JsonrpcNotificationFrame`, ...) live in `transports/ws_client.ts`
 *     so both in-process and cross-process drivers reference one source.
 *
 * Hono's wire upgrade is skipped — the Node test runtime has no
 * `@hono/node-ws` adapter — but the full dispatch path is exercised
 * (per-action auth, input validation, `ctx.notify` back to the
 * originating socket, broadcast via `BackendWebsocketTransport`, and
 * close-on-revoke).
 *
 * @module
 */
import type { Context } from 'hono';
import { WSContext, type UpgradeWebSocket, type WSEvents } from 'hono/ws';
import { Logger } from '@fuzdev/fuz_util/log.js';
import { type Uuid } from '@fuzdev/fuz_util/id.js';
import type { ActionSpecUnion } from '../actions/action_spec.js';
import type { Action } from '../actions/action_types.js';
import type { ActionEventEnvironment } from '../actions/action_event_types.js';
import { type RegisterActionWsOptions } from '../actions/register_action_ws.js';
import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
import { type RequestContext } from '../auth/request_context.js';
import { type CredentialType } from '../hono_context.js';
import { type WsClient } from './transports/ws_client.js';
/**
 * A `WSContext` paired with capture arrays. Use `sends` to assert on
 * outgoing frames; use `closes` to assert on revocation / close.
 */
export interface FakeWs {
    ws: WSContext;
    sends: Array<string>;
    closes: Array<{
        code?: number;
        reason?: string;
    }>;
}
/**
 * Build a real `WSContext` backed by in-memory `send`/`close` capture.
 * Parsing of outgoing frames is left to the caller — `sends` holds the
 * raw strings as the dispatcher wrote them.
 */
export declare const create_fake_ws: () => FakeWs;
/** Options for `create_fake_hono_context`. */
export interface FakeHonoContextOptions {
    credential_type: CredentialType;
    /** A single role to grant via `create_test_request_context`. */
    role?: string;
    auth_session_id?: string | null;
    api_token_id?: string | null;
    /**
     * Override the `RequestContext` outright (for multi-role or custom
     * account/actor fixtures). Takes precedence over `role`.
     */
    request_context?: RequestContext;
}
/**
 * Build a fake Hono `Context` exposing the auth keys the dispatcher
 * reads via `c.get(...)`. Only `.get()` is populated — no other Hono
 * context surface is simulated.
 */
export declare const create_fake_hono_context: (opts: FakeHonoContextOptions) => Context;
/** The return of `create_stub_upgrade` — fake `upgradeWebSocket` + factory capture. */
export interface StubUpgrade {
    upgradeWebSocket: UpgradeWebSocket;
    get_create_events: () => (c: Context) => WSEvents | Promise<WSEvents>;
}
/**
 * Build a fake `upgradeWebSocket` that captures the `createEvents`
 * callback. The returned middleware is inert — tests drive
 * `createEvents` directly.
 */
export declare const create_stub_upgrade: () => StubUpgrade;
/**
 * Minimal `ActionEventEnvironment` for tests that instantiate an
 * `ActionPeer` without pulling in the full runtime. Pre-loads a
 * spec map from the supplied list.
 */
export declare class MinimalActionEnvironment implements ActionEventEnvironment {
    #private;
    executor: 'frontend' | 'backend';
    constructor(specs: ReadonlyArray<ActionSpecUnion>);
    lookup_action_handler(): undefined;
    lookup_action_spec(method: string): ActionSpecUnion | undefined;
}
/**
 * Hono types `WSEvents.onMessage` as `() => void | Promise<void>`.
 * Awaits only the Promise branch so tests observe full dispatch
 * (auth, validation, handler, send).
 */
export declare const dispatch_ws_message: (on_message: NonNullable<WSEvents["onMessage"]>, event: MessageEvent, ws: WSContext) => Promise<void>;
/** Auth identity for a mock connection. */
export interface WsConnectIdentity {
    /** Account id for the connection. Defaults to a fresh uuid per call. */
    account_id?: Uuid;
    /** Credential type. Defaults to `'session'`. Keeper actions require `'daemon_token'`. */
    credential_type?: CredentialType;
    /** Session id (any string). Defaults to a fresh uuid. Hashed by the dispatcher. */
    session_id?: string;
    /** Api token id; set for bearer connections, null otherwise. */
    api_token_id?: string | null;
    /** Roles to grant via active role_grants. Pass `[ROLE_KEEPER]` for keeper actions. */
    roles?: Array<string>;
}
/** Options for `create_ws_test_harness`. */
export interface CreateWsTestHarnessOptions {
    /**
     * The actions registered on this endpoint — matches the shape
     * `register_action_ws` accepts. Each entry is a `{spec, handler?}` tuple;
     * shared fuz_app primitives (like `heartbeat_action`) can be spread in
     * alongside consumer-specific actions.
     */
    actions: ReadonlyArray<Action>;
    /** Pass a pre-created transport to share with a broadcast API. */
    transport?: BackendWebsocketTransport;
    /**
     * Threaded through to `register_action_ws`. Defaults to `false` in tests —
     * fake timers + receive-silence detection need explicit opt-in and per-
     * test tuning to avoid spurious closes.
     */
    heartbeat?: RegisterActionWsOptions['heartbeat'];
    /** Optional logger. Defaults to a silent `[ws-test]` logger. */
    log?: Logger;
    /** Threaded straight through to `register_action_ws`. */
    on_socket_open?: RegisterActionWsOptions['on_socket_open'];
    /** Threaded straight through to `register_action_ws`. */
    on_socket_close?: RegisterActionWsOptions['on_socket_close'];
}
/** A harness instance — transport handle + connection factory. */
export interface WsTestHarness {
    transport: BackendWebsocketTransport;
    /**
     * Open a mock connection. Resolves after `on_socket_open` (and the
     * transport's `register_ws`) completes, so broadcasts issued
     * immediately after the `await` reach the connection. Earlier
     * revisions returned synchronously and required a `settle_open()`
     * microtask drain — no longer necessary.
     *
     * Returns the shared `WsClient` interface — same surface the
     * cross-process driver in `transports/ws_transport.ts` implements,
     * so assertion helpers and suite bodies work against either impl.
     */
    connect: (identity?: WsConnectIdentity) => Promise<WsClient>;
}
/**
 * Create a WebSocket test harness for the given specs + handlers.
 *
 * Registers against a throwaway Hono app with a fake
 * `upgradeWebSocket`; the captured events factory is invoked per
 * `connect()` with a synthesized Hono context carrying the requested
 * auth identity. Returned clients drive the real
 * `onOpen`/`onMessage`/`onClose` path against a real `WSContext`.
 */
export declare const create_ws_test_harness: (options: CreateWsTestHarnessOptions) => WsTestHarness;
/** Convenience: default identity for keeper-authenticated connections. */
export declare const keeper_identity: () => WsConnectIdentity;
/**
 * Wire a typed broadcast API against the harness's transport, matching
 * how a consumer's real backend composes the stack. Returns the typed
 * API so tests can call `.zap_run_created(...)` / `.workspace_changed(...)`
 * etc. directly.
 *
 * ```ts
 * const harness = create_ws_test_harness({actions});
 * const broadcast = build_broadcast_api<MyBackendActionsApi>({
 *   harness,
 *   specs: my_broadcast_action_specs,
 * });
 * const client = await harness.connect(keeper_identity());
 * await broadcast.zap_run_created({run_id: '...', ...});
 * await client.wait_for(is_notification('zap_run_created'));
 * ```
 */
export declare const build_broadcast_api: <TApi extends object>(options: {
    harness: WsTestHarness;
    specs: ReadonlyArray<ActionSpecUnion>;
}) => TApi;
//# sourceMappingURL=ws_round_trip.d.ts.map