From 854c18d96b95704dae8053671fcf5e78ba1dcd2c Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Sun, 3 Sep 2023 11:59:23 +0200 Subject: [PATCH 1/2] Add Plug.Test.sent_chunks/2 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"] --- lib/plug/adapters/test/conn.ex | 6 ++++-- lib/plug/test.ex | 28 ++++++++++++++++++++++++++- test/plug/adapters/test/conn_test.exs | 10 ++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/plug/adapters/test/conn.ex b/lib/plug/adapters/test/conn.ex index 6633ca40..54e6930a 100644 --- a/lib/plug/adapters/test/conn.ex +++ b/lib/plug/adapters/test/conn.ex @@ -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 diff --git a/lib/plug/test.ex b/lib/plug/test.ex index 3a1a774a..64e3c1b7 100644 --- a/lib/plug/test.ex +++ b/lib/plug/test.ex @@ -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 diff --git a/test/plug/adapters/test/conn_test.exs b/test/plug/adapters/test/conn_test.exs index 7bd31d3a..546d47e9 100644 --- a/test/plug/adapters/test/conn_test.exs +++ b/test/plug/adapters/test/conn_test.exs @@ -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, "/") From 951cdf62780401d017f242fb0af161d17151e121 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Wed, 29 Nov 2023 15:35:54 +0100 Subject: [PATCH 2/2] Add test --- test/plug/adapters/test/conn_test.exs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/plug/adapters/test/conn_test.exs b/test/plug/adapters/test/conn_test.exs index 546d47e9..11ed650a 100644 --- a/test/plug/adapters/test/conn_test.exs +++ b/test/plug/adapters/test/conn_test.exs @@ -124,6 +124,14 @@ defmodule Plug.Adapters.Test.ConnTest do assert Plug.Test.sent_chunks(conn) == ["foo", "bar"] end + test "sent chunks on send_resp" do + conn = conn(:get, "/") + conn = Plug.Conn.send_resp(conn, 200, "foobar") + + assert conn.resp_body == "foobar" + assert Plug.Test.sent_chunks(conn) == [] + end + test "inform adds to the informational responses to the list" do conn = conn(:get, "/")