> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Test Go Sends Without the Network

> Use reposttest to script attempts, record redacted requests, control time, and assert lifecycle events with native Go tests.

`github.com/repost-sh/repost-go/reposttest` drives the real generated method tree, validation, serialization, retry loop, response parsing, and observers without opening a network connection. It provides a scripted transport, deterministic generators, a manual clock and scheduler, retry entropy, and a recording observer.

## Build a deterministic client

```go theme={"languages":{"custom":["/languages/repost.json"]}}
func TestOrderCreated(t *testing.T) {
    fixedTime := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
    transport := reposttest.NewScriptedTransport().EnqueueResponse(
        http.StatusAccepted,
        `{"id":"msg_test","type":"order.created","customerId":"customer_test","timestamp":"2026-01-01T00:00:00.000Z"}`,
        [2]string{"content-type", "application/json"},
    )
    maxAttempts := 1

    client, err := repostclient.NewClient(repost.ClientOptions{
        APIKey:      "test-key",
        Transport:   transport,
        MaxAttempts: &maxAttempts,
        Generators: reposttest.FixedGenerators(
            fixedTime,
            "00000000-0000-4000-8000-000000000000",
            "cabcdefghijklmnopqrstuvw",
        ),
    })
    if err != nil {
        t.Fatal(err)
    }
    t.Cleanup(func() { _ = client.Close() })

    result, err := client.Webhooks.Order.Created(context.Background(), repostclient.OrderCreatedInput{
        CustomerID:     "customer_test",
        Data:           repostclient.Order{ID: "order_test"},
        IdempotencyKey: "order_test:created",
    })
    if err != nil {
        t.Fatal(err)
    }

    if result.ID != "msg_test" {
        t.Fatalf("result ID = %q", result.ID)
    }
    requests := transport.Requests()
    if len(requests) != 1 || requests[0].IdempotencyKey != "order_test:created" {
        t.Fatalf("requests = %#v", requests)
    }
    if !bytes.Contains(requests[0].Body, []byte(`"id":"order_test"`)) {
        t.Fatalf("body = %s", requests[0].Body)
    }
}
```

`Requests` returns deep copies in capture order. Each record contains the URL, serialized body, attempt number, idempotency key, connect timeout, attempt timeout, and safe headers. The `Authorization` header is always removed before storage.

A scripted `202` response must contain the same event type, customer ID, and timestamp that the request sent. This matches publish API response validation and catches stale test fixtures.

## Script failures and pending attempts

`ScriptedTransport` consumes one scripted result per attempt:

```go theme={"languages":{"custom":["/languages/repost.json"]}}
transport := reposttest.NewScriptedTransport().
    EnqueueFailure(repost.ErrorCodeConnect, repost.FailureReasonConnectRefused, false).
    EnqueueResponse(http.StatusAccepted, acceptedBody,
        [2]string{"content-type", "application/json"})
```

The `committed` argument tells the runtime whether request bytes may have left. Use `false` for a definite `NOT_SENT` failure and `true` when the expected outcome is ambiguous.

`EnqueuePending` returns a `ControlledResponse`. Complete it with a response, fail it with a classified transport error, or cancel it after the send is in flight. The first settlement wins. Script exhaustion is a test defect and becomes a non-retryable custom-transport failure instead of falling through to the network.

## Drive retry timing

Use one `ManualClock` for monotonic and wall time, and pair it with `ManualScheduler`. The scheduler blocks retry waits until the test advances the clock.

```go theme={"languages":{"custom":["/languages/repost.json"]}}
clock := reposttest.NewManualClock(fixedTime)
scheduler := reposttest.NewManualScheduler(clock)
maxAttempts := 2

client, err := repostclient.NewClient(repost.ClientOptions{
    APIKey:         "test-key",
    Transport:      transport,
    MaxAttempts:    &maxAttempts,
    RetryEntropy:   reposttest.SequenceEntropy(250),
    MonotonicClock: clock.Now,
    WallClock:       clock.WallNow,
    Scheduler:       scheduler,
})

completed := make(chan error, 1)
go func() {
    _, sendErr := client.Webhooks.Order.Created(ctx, input)
    completed <- sendErr
}()

if err := scheduler.AwaitSleepCount(ctx, 1); err != nil {
    t.Fatal(err)
}
clock.Advance(250 * time.Millisecond)
if err := <-completed; err != nil {
    t.Fatal(err)
}
```

Use `SequenceEntropy` when you need exact jitter values. `SequenceGenerators` and `SequenceIdempotencyKeys` provide concurrency-safe FIFO values. Sequence helpers fail on exhaustion so unexpected generator or retry calls cannot pass silently.

## Assert lifecycle events

`RecordingObserver` exposes immutable event snapshots and context-aware wait methods:

```go theme={"languages":{"custom":["/languages/repost.json"]}}
recorder := reposttest.NewRecordingObserver()
client, err := repostclient.NewClient(repost.ClientOptions{
    APIKey:    "test-key",
    Transport: transport,
    Observer:  recorder.Observer(),
})

_, err = client.Webhooks.Order.Created(ctx, input)
if err != nil {
    t.Fatal(err)
}
if err := recorder.AwaitQuiescence(ctx); err != nil {
    t.Fatal(err)
}

events := recorder.Events()
```

For a one-attempt success, assert `operation.start`, `attempt.start`, `attempt.end`, then `operation.end`. Use `AwaitNext` when the test needs to react to a specific event before allowing an attempt to settle.

No process-wide network guard is needed. A client configured with `ScriptedTransport` has no path to the built-in HTTP transport.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Reliability" icon="shield-check" href="/docs/send/go/reliability" cta="Outcomes" arrow="true">
    Choose the delivery states and error codes to exercise.
  </Card>

  <Card title="Observability" icon="activity" href="/docs/send/go/observability" cta="Observe" arrow="true">
    Understand the events recorded by the test observer.
  </Card>

  <Card title="Frameworks" icon="boxes" href="/docs/send/go/frameworks" cta="Integrate" arrow="true">
    Share and close one client in a Go service.
  </Card>
</Columns>
