Skip to content

Commit

Permalink
Add Plug.Test.sent_chunks/2
Browse files Browse the repository at this point in the history
Before this patch there was no way to reconstruct the invidual chunks
that were sent. All we got was the full resulting body in
`conn.resp_body`.

For completeness, I believe instead of messaging we could store chunks in a list
in the test adapter state and simply append:

    - def send_chunked(state, _status, _headers), do: {:ok, "", %{state | chunks: ""}}
    + def send_chunked(state, _status, _headers), do: {:ok, "", %{state | chunks: []}}

    - def chunk(%{owner: owner, ref: ref, chunks: chunks} = state, chunk) do
    -   send(owner, {ref, :chunk, chunk})
    -   body = chunks <> IO.iodata_to_binary(chunk)
    -   {:ok, body, %{state | chunks: body}}
    - end
    + def chunk(%{owner: owner, ref: ref, chunks: chunks} = state, chunk) do
    +   chunk = IO.iodata_to_binary(chunk)
    +   body = IO.iodata_to_binary([chunks, chunk])
    +   {:ok, body, %{state | chunks: chunks ++ [chunk]}}
    + end

but I was following the existing functions `sent_informs` and `sent_upgrades`.

My use case is to be able to test response streaming using Req :plug
adapter:

    req =
      Req.new(
        plug: fn conn ->
          conn = Plug.Conn.send_chunked(conn, 200)
          {:ok, conn} = Plug.Conn.chunk(conn, "foo")
          {:ok, conn} = Plug.Conn.chunk(conn, "bar")
          conn
        end,
        into: []
      )

    resp = Req.request!(req)
    assert resp.body == ["foo", "bar"]
  • Loading branch information
wojtekmach committed Sep 3, 2023
1 parent 7b859ef commit 7952914
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 3 deletions.
6 changes: 4 additions & 2 deletions lib/plug/adapters/test/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ defmodule Plug.Adapters.Test.Conn do

def chunk(%{method: "HEAD"} = state, _body), do: {:ok, "", state}

def chunk(%{chunks: chunks} = state, body) do
body = chunks <> IO.iodata_to_binary(body)
def chunk(%{owner: owner, ref: ref, chunks: chunks} = state, chunk) do
chunk = IO.iodata_to_binary(chunk)
send(owner, {ref, :chunk, chunk})
body = chunks <> chunk
{:ok, body, %{state | chunks: body}}
end

Expand Down
28 changes: 27 additions & 1 deletion lib/plug/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,39 @@ defmodule Plug.Test do
end
end

@doc """
Returns the sent body chunks.
This function depends on gathering the messages sent by the test adapter when
response body chunks are sent. Calling this function will clear the chunk
messages from the inbox for the process. To assert on multiple informs, the
result of the function should be stored in a variable.
## Examples
conn = conn(:get, "/")
assert Plug.Test.sent_chunks(conn) == ["foo", "bar"]
"""
def sent_chunks(%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
Enum.reverse(receive_chunks(ref, []))
end

defp receive_chunks(ref, chunks) do
receive do
{^ref, :chunk, chunk} ->
receive_chunks(ref, [chunk | chunks])
after
0 -> chunks
end
end

@doc """
Returns the informational requests that have been sent.
This function depends on gathering the messages sent by the test adapter when
informational messages, such as an early hint, are sent. Calling this
function will clear the informational request messages from the inbox for the
process. To assert on multiple informs, the result of the function should be
process. To assert on multiple informs, the result of the function should be
stored in a variable.
## Examples
Expand Down
10 changes: 10 additions & 0 deletions test/plug/adapters/test/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ defmodule Plug.Adapters.Test.ConnTest do
assert child_conn.host == "www.elixir-lang.org"
end

test "sent chunks" do
conn = conn(:get, "/")
conn = Plug.Conn.send_chunked(conn, 200)
{:ok, conn} = Plug.Conn.chunk(conn, "foo")
{:ok, conn} = Plug.Conn.chunk(conn, "bar")

assert conn.resp_body == "foobar"
assert Plug.Test.sent_chunks(conn) == ["foo", "bar"]
end

test "inform adds to the informational responses to the list" do
conn =
conn(:get, "/")
Expand Down

0 comments on commit 7952914

Please sign in to comment.