# `Stampede.Channels`
[🔗](https://github.com/mandar1045/stampede/blob/v0.2.0/lib/stampede/channels.ex#L1)

A Phoenix Channels client for load tests.

    {:ok, socket} = Stampede.Channels.connect("ws://localhost:4000/socket", params: %{token: token})
    {:ok, _reply} = Stampede.Channels.join(socket, "room:lobby")
    {:ok, _reply} = Stampede.Channels.push(socket, "room:lobby", "new_msg", %{body: "hi"})
    {:ok, payload} = Stampede.Channels.await(socket, "room:lobby", "new_msg")

Each socket is a process linked to the caller. It sends heartbeats, matches
every reply to the request it answers, and keeps the last 1,000 messages
pushed or broadcast by the server until they are read with `await/4`.

## Metrics

Inside a run, sockets record:

  * `"ws.connect"` and `"ws.connect.errors"`
  * `"ws.disconnects"` - connections closed by the server or the network
  * `"channel.join"` and `"channel.join.errors"`
  * `"channel.push"` and `"channel.push.errors"`
  * `"channel.leave"` and `"channel.leave.errors"`
  * `"channel.messages"` - messages pushed or broadcast by the server
  * `"channel.messages.dropped"` - messages dropped because the socket
    already held 1,000 unread ones

Error replies, timeouts and closed connections all count as errors.

# `socket`

```elixir
@type socket() :: pid()
```

# `await`

```elixir
@spec await(socket(), String.t(), String.t(), timeout() | keyword()) ::
  {:ok, term()} | {:error, term()}
```

Waits until the server pushes or broadcasts `event` on `topic`, and returns
its payload.

Messages that arrived before the call are looked at first, oldest first.
Returns `{:error, :timeout}` if no such message arrives in time.

The last argument is a timeout in milliseconds, or a list of options:

  * `:timeout` - how long to wait, in milliseconds. Defaults to `5_000`.
  * `:match` - a function that gets each payload and returns whether it is
    the one to wait for. Useful when other VUs broadcast the same event:

        Stampede.Channels.await(socket, "room:lobby", "shout", match: &(&1["from"] == vu.id))

# `cast`

```elixir
@spec cast(socket(), String.t(), String.t(), map()) :: :ok
```

Pushes `event` with `payload` to `topic` without waiting for a reply.

# `close`

```elixir
@spec close(socket()) :: :ok
```

Closes the connection and waits for the socket to stop.

# `connect`

```elixir
@spec connect(String.t(), keyword()) :: {:ok, socket()} | {:error, term()}
```

Connects to the Phoenix socket at `url`.

`url` is the socket's endpoint as given to the JavaScript client, such as
`"ws://localhost:4000/socket"`. `/websocket` is added to its path unless it
is already there.

## Options

  * `:params` - query parameters sent with the connection, often used for
    authentication
  * `:headers` - extra headers for the upgrade request
  * `:vsn` - the serializer version, `"2.0.0"` (the default) or `"1.0.0"`.
    A `vsn` query parameter in `url` takes precedence.
  * `:connect_timeout` - how long to wait for the connection, in
    milliseconds. Defaults to `10_000`.
  * `:heartbeat_interval` - how often to send a heartbeat, in milliseconds,
    or `nil` to never send one. Defaults to `30_000`, like the JavaScript
    client. The connection is closed when a heartbeat is still unanswered
    by the time the next one is due.
  * `:transport_opts` - options for `:gen_tcp` or `:ssl`, given to
    `Mint.HTTP.connect/4`

Returns `{:error, reason}` when the connection fails, such as a
`Mint.TransportError` when nothing is listening, or a
`Mint.WebSocket.UpgradeFailureError` when the socket's `connect/3`
refuses it.

# `join`

```elixir
@spec join(socket(), String.t(), map(), keyword()) :: {:ok, term()} | {:error, term()}
```

Joins `topic`, sending `payload` to the channel's `join/3` callback.

Returns `{:ok, response}` when the channel accepts the join and
`{:error, response}` when it refuses it. It may also return
`{:error, :timeout}`, or `{:error, {:closed, reason}}` when the connection
closes first.

## Options

  * `:timeout` - how long to wait for the reply, in milliseconds. Defaults
    to `5_000`.
  * `:metric` - the timing to record the join as, or `nil` to record
    nothing. Defaults to `"channel.join"`.

# `leave`

```elixir
@spec leave(socket(), String.t(), keyword()) :: :ok | {:error, term()}
```

Leaves `topic`.

Takes the same options as `join/4`, with `:metric` defaulting to
`"channel.leave"`.

# `push`

```elixir
@spec push(socket(), String.t(), String.t(), map(), keyword()) ::
  {:ok, term()} | {:error, term()}
```

Pushes `event` with `payload` to `topic` and waits for the reply.

Returns `{:ok, response}` or `{:error, response}`, depending on what the
channel's `handle_in/3` replied. Use `cast/4` for events the channel does
not reply to.

Takes the same options as `join/4`, with `:metric` defaulting to
`"channel.push"`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
