Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions test/tesla/adapter/finch_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,21 @@ defmodule Tesla.Adapter.FinchTest do
# Before the fix, an error during streaming would cause:
# (CaseClauseError) no case clause matching: {:error, error, nil}

assert {:error, _} =
{:ok, listen} = :gen_tcp.listen(0, ip: {127, 0, 0, 1})
{:ok, port} = :inet.port(listen)
:ok = :gen_tcp.close(listen)

assert {:error, :econnrefused} =
Tesla.Adapter.Finch.call(
%Tesla.Env{
method: :get,
url: "http://nonexistent.invalid",
url: "http://127.0.0.1:#{port}",
body: nil,
headers: []
},
name: @finch_name,
response: :stream,
receive_timeout: 1000
receive_timeout: 5_000
)
end

Expand Down
19 changes: 19 additions & 0 deletions test/tesla/adapter/gun_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,25 @@ defmodule Tesla.Adapter.GunTest do
assert Enum.join(stream) == "firstsecond"
end

test "joins a plain body that arrives in more than one part" do
url =
start_raw_server(fn socket ->
:gen_tcp.send(socket, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n")
Process.sleep(50)
:gen_tcp.send(socket, "5\r\nfirst\r\n")
Process.sleep(50)
:gen_tcp.send(socket, "6\r\nsecond\r\n")
Process.sleep(50)
Comment thread
yordis marked this conversation as resolved.
Outdated
:gen_tcp.send(socket, "0\r\n\r\n")
Process.sleep(50)
:gen_tcp.close(socket)
end)

request = %Env{method: :get, url: url}

assert {:ok, %Env{status: 200, body: "firstsecond"}} = call(request, timeout: 2_000)
end

defp start_raw_server(on_request, opts \\ []) do
{:ok, listen_socket} =
:gen_tcp.listen(0, [:binary, packet: :raw, active: false, reuseaddr: true])
Expand Down
99 changes: 99 additions & 0 deletions test/tesla/adapter/mint_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ defmodule Tesla.Adapter.MintTest do
@large_http2_request_size 70_000
@default_connection_window_size 65_535
@wide_stream_window_size 1_048_576
@chunked_head "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n"
@empty_head "HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n"

use Tesla.AdapterCase.SSL,
transport_opts: [
Expand Down Expand Up @@ -1466,6 +1468,103 @@ defmodule Tesla.Adapter.MintTest do
end
end

describe "responses split into scripted packets" do
setup do
{:ok, listen_socket} = :gen_tcp.listen(0, [:binary, active: false, reuseaddr: true])
{:ok, port} = :inet.port(listen_socket)
{:ok, conn} = Mint.HTTP.connect(:http, "localhost", port, mode: :active)

on_exit(fn ->
Mint.HTTP.close(conn)
:gen_tcp.close(listen_socket)
end)

{:ok, conn: conn, url: "http://localhost:#{port}", original: "localhost:#{port}"}
end

test "streams a body that arrived whole with the headers", context do
assert {:ok, %Env{body: body}} =
call_with_packets(context, [@chunked_head <> "5\r\nhello\r\n0\r\n\r\n"],
body_as: :stream
)

assert Enum.to_list(body) == ["hello"]
end

test "streams nothing when the headers finish the response", context do
assert {:ok, %Env{body: body}} =
call_with_packets(context, [@empty_head], body_as: :stream)

assert Enum.to_list(body) == []
end

test "streams data that came with the headers before the final packet", context do
assert {:ok, %Env{body: body}} =
call_with_packets(context, [@chunked_head <> "5\r\nhello\r\n", "0\r\n\r\n"],
body_as: :stream
)

assert Enum.to_list(body) == ["hello"]
end

test "streams data from packets that follow the headers", context do
packets = [@chunked_head, "5", "\r\nhello\r\n", "5\r\nworld\r\n0\r\n\r\n"]

assert {:ok, %Env{body: body}} = call_with_packets(context, packets, body_as: :stream)

assert Enum.to_list(body) == ["hello", "world"]
end

test "returns the first chunk the headers packet finished", context do
assert {:ok, %Env{body: %{body: {:fin, "hello"}}}} =
call_with_packets(context, [@chunked_head <> "5\r\nhello\r\n0\r\n\r\n"],
body_as: :chunks
)
end

test "returns the first chunk the headers packet left open", context do
assert {:ok, %Env{body: %{body: {:nofin, "hello"}}}} =
call_with_packets(context, [@chunked_head <> "5\r\nhello\r\n"], body_as: :chunks)
end

test "returns an empty final chunk when the headers finish the response", context do
assert {:ok, %Env{body: %{body: {:fin, ""}}}} =
call_with_packets(context, [@empty_head], body_as: :chunks)
end

test "reads the chunks of packets that follow the headers", context do
packets = [@chunked_head, "5\r\nhello\r\n", "0\r\n\r\n"]

assert {:ok, %Env{body: %{conn: conn, ref: ref, opts: opts, body: {:nofin, ""}}}} =
call_with_packets(context, packets, body_as: :chunks)

assert {:nofin, conn, "hello"} = Tesla.Adapter.Mint.read_chunk(conn, ref, opts)
assert {:fin, _conn, ""} = Tesla.Adapter.Mint.read_chunk(conn, ref, opts)
end

test "joins a plain body delivered across packets", context do
packets = [@chunked_head, "5\r\nhello\r\n", "5\r\nworld\r\n", "0\r\n\r\n"]

assert {:ok, %Env{body: "helloworld"}} = call_with_packets(context, packets, max_body: 10)
end

test "rejects a plain body that outgrows max_body across packets", context do
packets = [@chunked_head, "5\r\nhello\r\n", "5\r\nworld\r\n", "0\r\n\r\n"]

assert {:error, :body_too_large} = call_with_packets(context, packets, max_body: 7)
end
end

defp call_with_packets(%{conn: conn, url: url, original: original}, packets, opts) do
socket = Mint.HTTP.get_socket(conn)
Enum.each(packets, &send(self(), {:tcp, socket, &1}))

call(
%Env{method: :get, url: url},
[conn: conn, original: original, mode: :active, close_conn: false] ++ opts
)
end

defp streamed_dispatch do
:cowboy_router.compile([
{:_,
Expand Down
Loading