Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
59 changes: 59 additions & 0 deletions test/support/tls.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Tesla.TestSupport.TLS do
@moduledoc """
TLS material for the local HTTPS test servers, generated once per test run.

The certificate bundled with httparrot expires, so tests use a freshly issued
chain instead of files that can go stale.
"""

@key {__MODULE__, :files}

def generate! do
%{server_config: server_config, client_config: client_config} =
:public_key.pkix_test_data(%{
server_chain: %{
root: cert_opts(),
peer: [extensions: [localhost_subject_alt_name()]] ++ cert_opts()
},
client_chain: %{root: cert_opts(), peer: cert_opts()}
})

dir = Path.join(System.tmp_dir!(), "tesla-test-tls-#{System.unique_integer([:positive])}")
File.mkdir_p!(dir)

{key_type, key_der} = Keyword.fetch!(server_config, :key)

files = %{
cacertfile:
write_pem!(dir, "ca.crt", Enum.map(client_config[:cacerts], &{:Certificate, &1})),
certfile:
write_pem!(dir, "server.crt", [{:Certificate, Keyword.fetch!(server_config, :cert)}]),
keyfile: write_pem!(dir, "server.key", [{key_type, key_der}])
}

:persistent_term.put(@key, files)
files
end

def cacertfile, do: fetch!(:cacertfile)
def certfile, do: fetch!(:certfile)
def keyfile, do: fetch!(:keyfile)

defp fetch!(name), do: Map.fetch!(:persistent_term.get(@key), name)

defp write_pem!(dir, name, entries) do
path = Path.join(dir, name)

pem =
:public_key.pem_encode(Enum.map(entries, fn {type, der} -> {type, der, :not_encrypted} end))

File.write!(path, pem)
path
end

defp cert_opts, do: [key: {:namedCurve, :secp256r1}, digest: :sha256]

defp localhost_subject_alt_name do
{:Extension, {2, 5, 29, 17}, false, [dNSName: ~c"localhost", iPAddress: <<127, 0, 0, 1>>]}
Comment thread
yordis marked this conversation as resolved.
end
end
2 changes: 1 addition & 1 deletion test/tesla/adapter/finch_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Tesla.Adapter.FinchTest do
pools: %{
@https => [
conn_opts: [
transport_opts: [cacertfile: "#{:code.priv_dir(:httparrot)}/ssl/server-ca.crt"]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
]
]
}
Expand Down
6 changes: 3 additions & 3 deletions test/tesla/adapter/gun_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Tesla.Adapter.GunTest do
use Tesla.AdapterCase.SSL,
certificates_verification: true,
transport_opts: [
cacertfile: Path.join([to_string(:code.priv_dir(:httparrot)), "/ssl/server-ca.crt"])
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]

alias Tesla.Adapter.Gun
Expand Down Expand Up @@ -119,7 +119,7 @@ defmodule Tesla.Adapter.GunTest do
call(request,
certificates_verification: true,
transport_opts: [
cacertfile: "#{:code.priv_dir(:httparrot)}/ssl/server-ca.crt"
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]
)
end
Expand Down Expand Up @@ -678,7 +678,7 @@ defmodule Tesla.Adapter.GunTest do
call(request,
tls_opts: [
verify: :verify_peer,
cacertfile: "#{:code.priv_dir(:httparrot)}/ssl/server-ca.crt"
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]
)
end
Expand Down
2 changes: 1 addition & 1 deletion test/tesla/adapter/hackney_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Tesla.Adapter.HackneyTest do
use Tesla.AdapterCase.SSL,
ssl_options: [
verify: :verify_peer,
cacertfile: Path.join([to_string(:code.priv_dir(:httparrot)), "/ssl/server-ca.crt"])
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]

alias Tesla.Env
Expand Down
2 changes: 1 addition & 1 deletion test/tesla/adapter/httpc_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Tesla.Adapter.HttpcTest do
use Tesla.AdapterCase.SSL,
ssl: [
verify: :verify_peer,
cacertfile: Path.join([to_string(:code.priv_dir(:httparrot)), "/ssl/server-ca.crt"])
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]

# :httpc accepts only a fixed set of method atoms, which as of OTP 29 does
Expand Down
2 changes: 1 addition & 1 deletion test/tesla/adapter/ibrowse_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Tesla.Adapter.IbrowseTest do
# use Tesla.AdapterCase.SSL,
# ssl_options: [
# verify: :verify_peer,
# cacertfile: Path.join([to_string(:code.priv_dir(:httparrot)), "/ssl/server-ca.crt"])
# cacertfile: Tesla.TestSupport.TLS.cacertfile()
# ]

# ibrowse supports only a fixed set of method atoms and would crash its
Expand Down
58 changes: 24 additions & 34 deletions test/tesla/adapter/mint_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use Tesla.AdapterCase.SSL,
transport_opts: [
cacertfile: Path.join([to_string(:code.priv_dir(:httparrot)), "/ssl/server-ca.crt"])
cacertfile: Tesla.TestSupport.TLS.cacertfile()
]

test "timeout request" do
Expand Down Expand Up @@ -312,7 +312,7 @@
assert {:ok, %Env{} = response} =
call(request,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
)

assert response.status == 200
Expand All @@ -336,7 +336,7 @@
assert {:ok, %Env{} = response} =
call(request,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
)

assert response.status == 200
Expand All @@ -357,7 +357,7 @@
assert {:ok, %Env{} = response} =
call(request,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
)

assert response.status == 200
Expand All @@ -380,7 +380,7 @@
assert {:ok, %Env{} = response} =
call(request,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
)

assert response.status == 200
Expand All @@ -402,7 +402,7 @@
assert {:ok, %Env{} = response} =
call(request,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()]
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()]
)

assert response.status == 200
Expand All @@ -414,15 +414,14 @@
setup do
listener_ref = :"mint-early-response-#{System.unique_integer([:positive])}"
dispatch = early_response_dispatch()
priv_dir = :code.priv_dir(:httparrot)

{:ok, _pid} =
:cowboy.start_tls(
listener_ref,
[
port: 0,
certfile: priv_dir ++ ~c"/ssl/server.crt",
keyfile: priv_dir ++ ~c"/ssl/server.key"
certfile: Tesla.TestSupport.TLS.certfile(),
keyfile: Tesla.TestSupport.TLS.keyfile()
],
%{env: %{dispatch: dispatch}}
)
Expand All @@ -433,7 +432,7 @@

{:ok,
early_response_url: "https://localhost:#{port}",
early_response_cacertfile: Path.join([to_string(priv_dir), "ssl/server-ca.crt"])}
early_response_cacertfile: Tesla.TestSupport.TLS.cacertfile()}
end

test "returns the response body without waiting for another packet", %{
Expand Down Expand Up @@ -508,15 +507,14 @@
describe "issue #394 - handle HTTP/2 connection window exhaustion" do
setup do
listener_ref = :"mint-connection-window-#{System.unique_integer([:positive])}"
priv_dir = :code.priv_dir(:httparrot)

{:ok, _pid} =
:cowboy.start_tls(
listener_ref,
[
port: 0,
certfile: priv_dir ++ ~c"/ssl/server.crt",
keyfile: priv_dir ++ ~c"/ssl/server.key"
certfile: Tesla.TestSupport.TLS.certfile(),
keyfile: Tesla.TestSupport.TLS.keyfile()
],
%{
env: %{dispatch: upload_echo_dispatch()},
Expand All @@ -532,7 +530,7 @@

{:ok,
upload_url: "https://localhost:#{port}",
upload_cacertfile: Path.join([to_string(priv_dir), "ssl/server-ca.crt"])}
upload_cacertfile: Tesla.TestSupport.TLS.cacertfile()}
end

test "uploads a body that exhausts the connection window before the stream window", %{
Expand Down Expand Up @@ -712,15 +710,14 @@
setup do
listener_ref = @internal_error_listener_ref
dispatch = internal_error_dispatch()
priv_dir = :code.priv_dir(:httparrot)

{:ok, _pid} =
:cowboy.start_tls(
listener_ref,
[
port: 0,
certfile: priv_dir ++ ~c"/ssl/server.crt",
keyfile: priv_dir ++ ~c"/ssl/server.key"
certfile: Tesla.TestSupport.TLS.certfile(),
keyfile: Tesla.TestSupport.TLS.keyfile()
],
%{
env: %{dispatch: dispatch},
Expand All @@ -734,7 +731,7 @@

{:ok,
reset_url: "https://localhost:#{port}",
reset_cacertfile: Path.join([to_string(priv_dir), "ssl/server-ca.crt"])}
reset_cacertfile: Tesla.TestSupport.TLS.cacertfile()}
end

test "Mint emits server_closed_request from a live HTTP/2 peer", %{
Expand Down Expand Up @@ -819,7 +816,7 @@
)
end

test "Tesla adapter raises the Mint request error while enumerating stream bodies", %{

Check failure on line 819 in test/tesla/adapter/mint_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.19.0 OTP 28.5)

test issue #553 - prove real HTTP/2 request resets Tesla adapter raises the Mint request error while enumerating stream bodies (Tesla.Adapter.MintTest)
reset_url: reset_url,
reset_cacertfile: reset_cacertfile
} do
Expand Down Expand Up @@ -878,23 +875,18 @@
Jason.decode!(body)
end

defp httparrot_cacertfile do
Path.join([to_string(:code.priv_dir(:httparrot)), "ssl/server-ca.crt"])
end

describe "issue #450 - handle missing Mint response types" do
setup do
listener_ref = @push_promise_listener_ref
dispatch = push_promise_dispatch()
priv_dir = :code.priv_dir(:httparrot)

{:ok, _pid} =
:cowboy.start_tls(
listener_ref,
[
port: 0,
certfile: priv_dir ++ ~c"/ssl/server.crt",
keyfile: priv_dir ++ ~c"/ssl/server.key"
certfile: Tesla.TestSupport.TLS.certfile(),
keyfile: Tesla.TestSupport.TLS.keyfile()
],
%{env: %{dispatch: dispatch}}
)
Expand All @@ -904,8 +896,7 @@
{_, port} = :ranch.get_addr(listener_ref)

{:ok,
push_url: "https://localhost:#{port}",
push_cacertfile: Path.join([to_string(priv_dir), "ssl/server-ca.crt"])}
push_url: "https://localhost:#{port}", push_cacertfile: Tesla.TestSupport.TLS.cacertfile()}
end

test "handles connection errors gracefully" do
Expand Down Expand Up @@ -1051,7 +1042,6 @@
describe "HTTP/2 connection shared with another request" do
setup do
listener_ref = :"mint-shared-http2-#{System.unique_integer([:positive])}"
priv_dir = :code.priv_dir(:httparrot)

dispatch =
:cowboy_router.compile([
Expand All @@ -1067,8 +1057,8 @@
listener_ref,
[
port: 0,
certfile: priv_dir ++ ~c"/ssl/server.crt",
keyfile: priv_dir ++ ~c"/ssl/server.key"
certfile: Tesla.TestSupport.TLS.certfile(),
keyfile: Tesla.TestSupport.TLS.keyfile()
],
%{
env: %{dispatch: dispatch},
Expand All @@ -1083,7 +1073,7 @@
{:ok, conn} =
Mint.HTTP.connect(:https, "localhost", port,
protocols: [:http2],
transport_opts: [cacertfile: httparrot_cacertfile()],
transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()],
mode: :passive
)

Expand Down Expand Up @@ -1175,15 +1165,15 @@
end

test "verifies the peer with the configured cacertfile" do
Application.put_env(:tesla, Tesla.Adapter.Mint, cacert: httparrot_cacertfile())
Application.put_env(:tesla, Tesla.Adapter.Mint, cacert: Tesla.TestSupport.TLS.cacertfile())

request = %Env{method: :get, url: "#{@https}/ip"}

assert {:ok, %Env{status: 200}} = call(request)
end

test "adds the configured cacertfile to the transport options it was given" do
Application.put_env(:tesla, Tesla.Adapter.Mint, cacert: httparrot_cacertfile())
Application.put_env(:tesla, Tesla.Adapter.Mint, cacert: Tesla.TestSupport.TLS.cacertfile())

request = %Env{method: :get, url: "#{@https}/ip"}

Expand All @@ -1196,7 +1186,7 @@
request = %Env{method: :get, url: "#{@https}/ip"}

assert {:ok, %Env{status: 200}} =
call(request, transport_opts: [cacertfile: httparrot_cacertfile()])
call(request, transport_opts: [cacertfile: Tesla.TestSupport.TLS.cacertfile()])
end
end

Expand Down
14 changes: 14 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
clients = [:ibrowse, :hackney, :gun, :finch, :castore, :mint]
Enum.map(clients, &Application.ensure_all_started/1)

tls = Tesla.TestSupport.TLS.generate!()
:ok = :cowboy.stop_listener(:https)

{:ok, _} =
:cowboy.start_tls(
:https,
[
port: Application.fetch_env!(:httparrot, :https_port),
certfile: tls.certfile,
keyfile: tls.keyfile
],
:ranch.get_protocol_options(:http)
)

Mox.defmock(Tesla.TestSupport.MockAdapter, for: Tesla.Adapter)

ExUnit.start()
Loading