From 4aca69c21974a38c6ef902c8e1f90f9d400839b2 Mon Sep 17 00:00:00 2001 From: Alexey Nikitin Date: Sat, 3 Apr 2021 16:53:30 +0300 Subject: [PATCH] Improvements - Lowercase method atoms - Use binaries both for header names and values - Introduce `raw_headers` which preserve headers case and order. [erlang/otp#2466](https://github.com/erlang/otp/pull/2466) allows to keep headers case - Some documentation updates - Update deps - Update CI to run tests inside docker container --- .github/workflows/erlang.yml | 14 +++---- README.md | 28 +++++++------ doc/README.md | 28 +++++++------ doc/bookish_spork_request.md | 21 +++++++--- doc/overview.edoc | 22 +++++----- elvis.config | 6 +++ rebar.config | 4 +- src/bookish_spork_handler.erl | 8 +++- src/bookish_spork_request.erl | 53 +++++++++++++++-------- test/bookish_spork_SUITE.erl | 38 ++++++++--------- test/bookish_spork_request_test.erl | 10 +++-- test/bookish_spork_test_helpers.hrl | 65 +++++++++++++++++++++++++++++ 12 files changed, 205 insertions(+), 92 deletions(-) create mode 100644 test/bookish_spork_test_helpers.hrl diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index a88cf72..dda6d9d 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -8,19 +8,19 @@ on: jobs: tests: + name: OTP ${{ matrix.otp }} runs-on: ubuntu-latest - name: OTP ${{ matrix.otp }} + + container: + image: erlang:${{ matrix.otp }} + strategy: matrix: - otp: [20.3, 21.3, 22.2] + otp: ['20.3', '21.3', '22.3', '23.3', '24.0-rc2'] steps: - - uses: actions/checkout@v2 - - - uses: gleam-lang/setup-erlang@v1.0.0 - with: - otp-version: ${{ matrix.otp }} + - uses: actions/checkout@v2.0.0 - name: Run tests run: rebar3 do eunit, ct, dialyzer diff --git a/README.md b/README.md index bc663f9..86ec691 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ # Bookish spork # -Copyright (c) 2018-2020 Alexey Nikitin +Copyright (c) 2018-2021 Alexey Nikitin -__Version:__ 0.3.6 +__Version:__ 0.4.0 __Authors:__ Alexey Nikitin ([`tank@bohr.su`](mailto:tank@bohr.su)) (_web site:_ [`https://twitter.com/tank_bohr`](https://twitter.com/tank_bohr)). @@ -52,7 +52,7 @@ First step: add to your rebar config {profiles, [ {test, [ {deps, [ - {bookish_spork, "0.3.6"} + {bookish_spork, "0.4.0"} ]} ]} ]}. @@ -111,13 +111,15 @@ It returns you an opaque structure of the request. You can inspect it with #### Bypass comparision #### -An elixir library [bypass](https://github.com/PSPDFKit-labs/bypass) does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test +An elixir library [bypass](https://github.com/PSPDFKit-labs/bypass) does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test. It's a beautiful library with great API, documentation, and very concise source code. If you are an elixir developer, most likely, it will be a good fit for you. -But bookish_spork has some advantages: +But nevertheless bookish_spork has some advantages: -* Bypass depends on `cowboy` and `plug`. Bookish spork has zero dependencies -* Bookish spork works seamlessly with both erlang and elixir. Bypass is supposed to be an elixir only library -* Bookish spork much simpler (I believe) +* Bypass depends on `cowboy` and `plug`. Bookish spork has zero dependencies. +* Bookish spork works seamlessly with both erlang and elixir. Bypass is supposed to be an elixir only library. +* Bookish spork much simpler (I believe) + (not any more). +* Bookish spork allows you to inspect the request very deeply and accurate. For example take a look at [`bookish_spork_request:raw_headers/1`](http://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md#raw_headers-1) and [`bookish_spork_request:ssl_info/1`](http://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md#ssl_info-1) and [`bookish_spork_request:tls_ext/1`](http://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md#tls_ext-1). It can be useful for HTTP clients testing. #### Elli comparision #### @@ -227,9 +229,9 @@ defmodule ChuckNorrisApiTest do use ExUnit.Case doctest ChuckNorrisApi - setup_all do - {:ok, _} = :bookish_spork.start_server - {:ok, %{}} + setup do + {:ok, _} = :bookish_spork.start_server() + on_exit(fn -> :bookish_spork.stop_server() end) end test "retrieves a random joke" do @@ -238,8 +240,8 @@ defmodule ChuckNorrisApiTest do }"]) assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up" - {:ok, request} = :bookish_spork.capture_request - assert request.uri == '/jokes/random' + {:ok, request} = :bookish_spork.capture_request() + assert request.uri === "/jokes/random" end end diff --git a/doc/README.md b/doc/README.md index 4a28e56..66b7966 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,9 +2,9 @@ # Bookish spork # -Copyright (c) 2018-2020 Alexey Nikitin +Copyright (c) 2018-2021 Alexey Nikitin -__Version:__ 0.3.6 +__Version:__ 0.4.0 __Authors:__ Alexey Nikitin ([`tank@bohr.su`](mailto:tank@bohr.su)) (_web site:_ [`https://twitter.com/tank_bohr`](https://twitter.com/tank_bohr)). @@ -52,7 +52,7 @@ First step: add to your rebar config {profiles, [ {test, [ {deps, [ - {bookish_spork, "0.3.6"} + {bookish_spork, "0.4.0"} ]} ]} ]}. @@ -111,13 +111,15 @@ It returns you an opaque structure of the request. You can inspect it with #### Bypass comparision #### -An elixir library [bypass](https://github.com/PSPDFKit-labs/bypass) does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test +An elixir library [bypass](https://github.com/PSPDFKit-labs/bypass) does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test. It's a beautiful library with great API, documentation, and very concise source code. If you are an elixir developer, most likely, it will be a good fit for you. -But bookish_spork has some advantages: +But nevertheless bookish_spork has some advantages: -* Bypass depends on `cowboy` and `plug`. Bookish spork has zero dependencies -* Bookish spork works seamlessly with both erlang and elixir. Bypass is supposed to be an elixir only library -* Bookish spork much simpler (I believe) +* Bypass depends on `cowboy` and `plug`. Bookish spork has zero dependencies. +* Bookish spork works seamlessly with both erlang and elixir. Bypass is supposed to be an elixir only library. +* Bookish spork much simpler (I believe) + (not any more). +* Bookish spork allows you to inspect the request very deeply and accurate. For example take a look at [`bookish_spork_request:raw_headers/1`](bookish_spork_request.md#raw_headers-1) and [`bookish_spork_request:ssl_info/1`](bookish_spork_request.md#ssl_info-1) and [`bookish_spork_request:tls_ext/1`](bookish_spork_request.md#tls_ext-1). It can be useful for HTTP clients testing. #### Elli comparision #### @@ -227,9 +229,9 @@ defmodule ChuckNorrisApiTest do use ExUnit.Case doctest ChuckNorrisApi - setup_all do - {:ok, _} = :bookish_spork.start_server - {:ok, %{}} + setup do + {:ok, _} = :bookish_spork.start_server() + on_exit(fn -> :bookish_spork.stop_server() end) end test "retrieves a random joke" do @@ -238,8 +240,8 @@ defmodule ChuckNorrisApiTest do }"]) assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up" - {:ok, request} = :bookish_spork.capture_request - assert request.uri == '/jokes/random' + {:ok, request} = :bookish_spork.capture_request() + assert request.uri === "/jokes/random" end end diff --git a/doc/bookish_spork_request.md b/doc/bookish_spork_request.md index cc809b3..21a7928 100644 --- a/doc/bookish_spork_request.md +++ b/doc/bookish_spork_request.md @@ -22,7 +22,7 @@ __abstract datatype__: `t()` ## Function Index ## -
body/1request body.
content_length/1Content-Length header value as intger.
from_transport/1
header/2Returns a particular header from request.
headers/1http headers map.
is_keepalive/1tells you if the request is keepalive or not https://tools.ietf.org/html/rfc6223
method/1http verb: 'GET', 'POST','PUT', 'DELETE', 'OPTIONS', ...
transport/1
uri/1path with query string.
version/1http protocol version tuple.
+
body/1request body.
content_length/1Content-Length header value as intger.
from_transport/1
header/2Returns a particular header from request.
headers/1HTTP headers map.
is_keepalive/1tells you if the request is keepalive or not https://tools.ietf.org/html/rfc6223
method/1http verb in lower case: get, post, put, delete, options, ...
raw_headers/1HTTP raw headers.
transport/1
uri/1path with query string.
version/1http protocol version tuple.
@@ -65,11 +65,11 @@ from_transport(Transport::bookish_sp ### header/2 ###

-header(Request::t(), HeaderName::string()) -> string() | nil
+header(Request::t(), HeaderName::string() | binary()) -> binary() | nil
 

-Returns a particular header from request. Header name is lowerced +Returns a particular header from request. @@ -80,7 +80,7 @@ headers(Request::t()) -> map()
-http headers map. Header names are normalized and lowercased +HTTP headers map. Header names are normalized and lowercased @@ -102,7 +102,18 @@ method(Request::t()) -> atom()
-http verb: 'GET', 'POST','PUT', 'DELETE', 'OPTIONS', ... +http verb in lower case: get, post, put, delete, options, ... + + + +### raw_headers/1 ### + +

+raw_headers(Request::t()) -> proplists:proplist()
+
+
+ +HTTP raw headers. Headers order and case are preserved diff --git a/doc/overview.edoc b/doc/overview.edoc index b0ad118..e93570e 100644 --- a/doc/overview.edoc +++ b/doc/overview.edoc @@ -112,17 +112,19 @@ It returns you an opaque structure of the request. You can inspect it with === Bypass comparision === -An elixir library bypass does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test +An elixir library bypass does pretty much the same. And illustrates the same approach. It starts a cowboy web-server to replace a real service for test. It's a beautiful library with great API, documentation, and very concise source code. If you are an elixir developer, most likely, it will be a good fit for you. -But bookish_spork has some advantages: +But nevertheless bookish_spork has some advantages: @@ -221,9 +223,9 @@ defmodule ChuckNorrisApiTest do use ExUnit.Case doctest ChuckNorrisApi - setup_all do - {:ok, _} = :bookish_spork.start_server - {:ok, %{}} + setup do + {:ok, _} = :bookish_spork.start_server() + on_exit(fn -> :bookish_spork.stop_server() end) end test "retrieves a random joke" do @@ -232,8 +234,8 @@ defmodule ChuckNorrisApiTest do }"]) assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up" - {:ok, request} = :bookish_spork.capture_request - assert request.uri == '/jokes/random' + {:ok, request} = :bookish_spork.capture_request() + assert request.uri === "/jokes/random" end end diff --git a/elvis.config b/elvis.config index 7cb1aff..5c46bd5 100644 --- a/elvis.config +++ b/elvis.config @@ -9,6 +9,12 @@ {elvis_style, function_naming_convention, #{ ignore => [bookish_spork_request], regex => "^([a-z][a-z0-9]*_?)*$" + }}, + {elvis_style, state_record_and_type, #{ + ignore => [ + bookish_spork_acceptor, + bookish_spork_blocking_queue + ] }} ], ruleset => erl_files diff --git a/rebar.config b/rebar.config index 7f1f30a..a4f1b28 100644 --- a/rebar.config +++ b/rebar.config @@ -10,7 +10,7 @@ {profiles, [ {test, [ {deps, [ - {gun, "1.3.1"} + {gun, "1.3.3"} ]}, {plugins, [ @@ -33,7 +33,7 @@ ]}, {elvis, [ {plugins, [ - {rebar3_lint, "0.1.11"} + {rebar3_lint, "0.4.0"} ]} ]} ]}. diff --git a/src/bookish_spork_handler.erl b/src/bookish_spork_handler.erl index 069f577..9d3ff1a 100644 --- a/src/bookish_spork_handler.erl +++ b/src/bookish_spork_handler.erl @@ -85,7 +85,8 @@ receive_request(#state{transport = Transport, request = RequestIn} = State) -> {ok, {http_request, Method, {abs_path, Uri}, Version}} -> RequestOut = bookish_spork_request:request_line(RequestIn, Method, Uri, Version), ?FUNCTION_NAME(State#state{request = RequestOut}); - {ok, {http_header, _, Header, _, Value}} -> + {ok, {http_header, _, NormalizedHeader, PreserveCaseHeader, Value}} -> + Header = choose_header(NormalizedHeader, PreserveCaseHeader), RequestOut = bookish_spork_request:add_header(RequestIn, Header, Value), ?FUNCTION_NAME(State#state{request = RequestOut}); {ok, http_eoh} -> @@ -136,6 +137,11 @@ complete_connection(State = #state{transport = Transport, request = Request}) -> {halt, normal} end. +choose_header(NormalizedHeader, undefined) -> + NormalizedHeader; +choose_header(_NormalizedHeader, PreserveCaseHeader) -> + PreserveCaseHeader. + reduce_while(State, []) -> {noreply, State}; reduce_while(State, [Fun|Rest]) -> diff --git a/src/bookish_spork_request.erl b/src/bookish_spork_request.erl index a310a92..5cc5dd0 100644 --- a/src/bookish_spork_request.erl +++ b/src/bookish_spork_request.erl @@ -20,6 +20,7 @@ version/1, header/2, headers/1, + raw_headers/1, body/1, body/2, socket/1, @@ -41,6 +42,7 @@ method := nil | atom(), uri := nil | binary(), version := nil | http_version(), + raw_headers := proplists:proplist(), headers := map(), body := nil | binary(), ssl_info := nil | proplists:proplist(), @@ -72,6 +74,7 @@ new() -> method => nil, uri => nil, version => nil, + raw_headers => [], headers => #{}, body => nil, ssl_info => nil, @@ -79,7 +82,7 @@ new() -> transport => nil }. --spec new(From :: list() | map() | ssl:sslsocket()) -> t(). +-spec new(From :: list() | map()) -> t(). %% @private new(List) when is_list(List) -> new(maps:from_list(List)); @@ -106,17 +109,23 @@ from_transport(Transport) -> request_line(Request, Method, Uri, Version) -> maps:merge(Request, #{ uri => list_to_binary(Uri), - method => Method, + method => lowercase_method(Method), version => Version }). --spec add_header(Request :: t(), Name :: string(), Value :: string()) -> t(). +-spec add_header(Request :: t(), Name :: atom() | binary(), Value :: string() | binary()) -> t(). %% @private add_header(Request, Name, Value) when is_atom(Name) -> - add_header(Request, atom_to_list(Name), Value); -add_header(#{ headers := Headers } = Request, Name, Value) -> - HeaderName = string:lowercase(Name), - maps:update(headers, maps:put(HeaderName, Value, Headers), Request). + add_header(Request, atom_to_binary(Name, utf8), Value); +add_header(Request, Name, Value) when is_list(Name) -> + add_header(Request, list_to_binary(Name), Value); +add_header(Request, Name, Value) when is_list(Value) -> + add_header(Request, Name, list_to_binary(Value)); +add_header(#{ raw_headers := RawHeaders0, headers := Headers0 } = Request, Name, Value) -> + RawHeaders = [{Name, Value} | RawHeaders0], + Headers = maps:put(string:lowercase(Name), Value, Headers0), + maps:update(headers, Headers, + maps:update(raw_headers, RawHeaders, Request)). -spec content_length(Request :: t()) -> integer(). %% @doc Content-Length header value as intger @@ -125,11 +134,11 @@ content_length(Request) -> nil -> 0; ContentLength -> - list_to_integer(ContentLength) + binary_to_integer(ContentLength) end. -spec method(Request :: t()) -> atom(). -%% @doc http verb: 'GET', 'POST','PUT', 'DELETE', 'OPTIONS', ... +%% @doc http verb in lower case: get, post, put, delete, options, ... method(#{ method := Method}) -> Method. @@ -143,16 +152,23 @@ uri(#{ uri := Uri}) -> version(#{ version := Version }) -> Version. --spec header(Request :: t(), HeaderName :: string()) -> string() | nil. -%% @doc Returns a particular header from request. Header name is lowerced -header(#{ headers := Headers }, HeaderName) -> - maps:get(HeaderName, Headers, nil). +-spec header(Request :: t(), HeaderName :: string() | binary()) -> binary() | nil. +%% @doc Returns a particular header from request. +header(Request, HeaderName) when is_list(HeaderName) -> + header(Request, list_to_binary(HeaderName)); +header(#{ headers := Headers }, HeaderName) when is_binary(HeaderName) -> + maps:get(string:lowercase(HeaderName), Headers, nil). -spec headers(Request :: t()) -> map(). -%% @doc http headers map. Header names are normalized and lowercased +%% @doc HTTP headers map. Header names are normalized and lowercased headers(#{ headers := Headers }) -> Headers. +-spec raw_headers(Request :: t()) -> proplists:proplist(). +%% @doc HTTP raw headers. Headers order and case are preserved +raw_headers(#{ raw_headers := RawHeaders }) -> + RawHeaders. + -spec body(Request :: t()) -> binary(). %% @doc request body body(#{ body := Body }) -> @@ -188,11 +204,14 @@ transport(#{transport := Transport}) -> -spec is_keepalive(Request :: t()) -> boolean(). %% @doc tells you if the request is keepalive or not [https://tools.ietf.org/html/rfc6223] -is_keepalive(#{ headers := #{"connection" := Conn }, version := {1, 0} }) -> - string:lowercase(Conn) =:= "keep-alive"; +is_keepalive(#{ headers := #{<<"connection">> := Conn }, version := {1, 0} }) -> + string:lowercase(Conn) =:= <<"keep-alive">>; is_keepalive(#{ version := {1, 0} }) -> false; -is_keepalive(#{ headers := #{"connection" := "close"}, version := {1, 1} }) -> +is_keepalive(#{ headers := #{<<"connection">> := <<"close">>}, version := {1, 1} }) -> false; is_keepalive(_) -> true. + +lowercase_method(Method) -> + binary_to_existing_atom(string:lowercase(atom_to_binary(Method, latin1)), latin1). diff --git a/test/bookish_spork_SUITE.erl b/test/bookish_spork_SUITE.erl index 37bfb8d..6ff6342 100644 --- a/test/bookish_spork_SUITE.erl +++ b/test/bookish_spork_SUITE.erl @@ -1,5 +1,6 @@ -module(bookish_spork_SUITE). +-include("bookish_spork_test_helpers.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -27,21 +28,18 @@ wait_for_async_request_completed_test/1, multiple_async_requests_test/1, multiple_async_with_closed_connection_requests_test/1, - capture_requests_test/1 + capture_requests_test/1, + raw_headers_test/1 ]). --define(CUSTOM_PORT, 9871). --define(HTTPC_SUCCESS(Status), {ok, {{_, Status, _}, _, _}}). --define(HTTPC_OK, ?HTTPC_SUCCESS(200)). --define(HTTPC_NO_CONTENT, ?HTTPC_SUCCESS(204)). - all() -> [base_integration_test, customized_response_test, failed_capture_test, stub_multiple_requests_test, stub_with_fun_test, keepalive_connection_test, without_keepalive_test, ssl_test, tls_ext_test, connection_id_test, request_when_there_is_no_stub_test, http_error_test, wait_for_async_request_completed_test, multiple_async_requests_test, - multiple_async_with_closed_connection_requests_test, capture_requests_test]. + multiple_async_with_closed_connection_requests_test, capture_requests_test, + raw_headers_test]. init_per_suite(Config) -> ok = application:ensure_started(inets), @@ -68,10 +66,10 @@ base_integration_test(_Config) -> {ok, {{"HTTP/1.1", 204, "No Content"}, _ResponseHeaders, _Body}} = httpc:request(get, {"http://localhost:32002/o/lo/lo?q=kjk", RequestHeaders}, [], []), {ok, Request} = bookish_spork:capture_request(), - ?assertEqual('GET', bookish_spork_request:method(Request)), + ?assertEqual(get, bookish_spork_request:method(Request)), ?assertEqual(<<"/o/lo/lo?q=kjk">>, bookish_spork_request:uri(Request)), ?assertEqual({1, 1}, bookish_spork_request:version(Request)), - ?assertMatch(#{"host" := "localhost:32002"}, bookish_spork_request:headers(Request)). + ?assertMatch(#{<<"host">> := <<"localhost:32002">>}, bookish_spork_request:headers(Request)). customized_response_test(Config) -> CustomPort = integer_to_list(?config(custom_port, Config)), @@ -88,11 +86,11 @@ customized_response_test(Config) -> ?assertEqual("test", proplists:get_value("x-custom-response-header", ResponseHeaders)), ?assertEqual(<<"Hello, Test">>, string:chomp(Body)), {ok, Request} = bookish_spork:capture_request(), - ?assertEqual('POST', bookish_spork_request:method(Request)), + ?assertEqual(post, bookish_spork_request:method(Request)), ?assertEqual(<<"/api/v1/users">>, bookish_spork_request:uri(Request)), ?assertEqual({1, 1}, bookish_spork_request:version(Request)), ?assertEqual(RequestBody, bookish_spork_request:body(Request)), - ?assertMatch(#{"accept" := "text/plain"}, bookish_spork_request:headers(Request)). + ?assertMatch(#{<<"accept">> := <<"text/plain">>}, bookish_spork_request:headers(Request)). failed_capture_test(_Config) -> ?assertMatch({error, _}, bookish_spork:capture_request(), "Got an error when there is no stub"). @@ -135,15 +133,7 @@ tls_ext_test(_Config) -> {"https://localhost:32002/tls", [{"Connection", "close"}]}, [], []), {ok, Request} = bookish_spork:capture_request(), TlsExt = bookish_spork_request:tls_ext(Request), - ?assertMatch(#{ - renegotiation_info := _, - ec_point_formats := _, - elliptic_curves := _, - signature_algs := _, - alpn := _, - sni := _, - srp := _ - }, TlsExt). + ?assertMatch(?TLS_EXT, TlsExt). -else. tls_ext_test(_Config) -> {skip, "Nothing to test"}. @@ -232,6 +222,14 @@ capture_requests_test(_Config) -> Requests = bookish_spork:capture_requests(), ?assertEqual([<<"/one">>, <<"/two">>], [bookish_spork_request:uri(Req) || Req <- Requests]). +raw_headers_test(_Config) -> + ok = bookish_spork:stub_request(), + ?HTTPC_NO_CONTENT = httpc:request(get, {"http://localhost:32002/test", ?CUSTOM_CASE_HEADERS}, + [], [{headers_as_is, true}]), + [Request] = bookish_spork:capture_requests(), + RawHeaders = bookish_spork_request:raw_headers(Request), + ?assertEqual(?RAW_HEADERS, RawHeaders, "Raw headers preserve order and case"). + gun_request(ConnectionPid) -> StreamRef = gun:get(ConnectionPid, "/"), {ok, Body} = gun:await_body(ConnectionPid, StreamRef), diff --git a/test/bookish_spork_request_test.erl b/test/bookish_spork_request_test.erl index c635cff..4ff2bd5 100644 --- a/test/bookish_spork_request_test.erl +++ b/test/bookish_spork_request_test.erl @@ -9,7 +9,7 @@ elixir_interface_test_() -> ?_assertEqual(Request, bookish_spork_request:'__struct__'(#{}))]. new_test_() -> - Method = 'POST', + Method = post, Uri = "/foo/bar", Version = {1, 1}, Body = <<"Hello">>, @@ -22,14 +22,16 @@ new_test_() -> method => Method, uri => list_to_binary(Uri), version => Version, - headers => #{"x-foo" => "Bar"}, + headers => #{<<"x-foo">> => <<"Bar">>}, + raw_headers => [{<<"X-Foo">>, <<"Bar">>}], body => Body }, List = [ {method, Method}, {uri, list_to_binary(Uri)}, {version, Version}, - {headers, #{"x-foo" => "Bar"}}, + {headers, #{<<"x-foo">> => <<"Bar">>}}, + {raw_headers, [{<<"X-Foo">>, <<"Bar">>}]}, {body, Body} ], [?_assertEqual(Request, bookish_spork_request:new(Map)), @@ -43,7 +45,7 @@ content_length_test_() -> add_header_test() -> Request = bookish_spork_request:add_header(bookish_spork_request:new(), "X-Lol", "kjk"), - ?assertEqual(#{"x-lol" => "kjk"}, bookish_spork_request:headers(Request), + ?assertEqual(#{<<"x-lol">> => <<"kjk">>}, bookish_spork_request:headers(Request), "Converts header name to lower case"). diff --git a/test/bookish_spork_test_helpers.hrl b/test/bookish_spork_test_helpers.hrl new file mode 100644 index 0000000..396c377 --- /dev/null +++ b/test/bookish_spork_test_helpers.hrl @@ -0,0 +1,65 @@ +-ifndef(BOOKISH_SPORK_TEST_HELPERS_HRL). +-define(BOOKISH_SPORK_TEST_HELPERS_HRL, true). + +-define(CUSTOM_PORT, 9871). +-define(HTTPC_SUCCESS(Status), {ok, {{_, Status, _}, _, _}}). +-define(HTTPC_OK, ?HTTPC_SUCCESS(200)). +-define(HTTPC_NO_CONTENT, ?HTTPC_SUCCESS(204)). + +-define(CUSTOM_CASE_HEADERS, [ + {"DNT", "1"}, + {"Connection", "close"}, + {"x-vEry-cAmEl-cAsE", "yEs"} +]). + +-ifdef(OTP_RELEASE). +-if(?OTP_RELEASE >= 23). +-define(RAW_HEADERS, [ + {<<"DNT">>, <<"1">>}, + {<<"Connection">>, <<"close">>}, + {<<"x-vEry-cAmEl-cAsE">>, <<"yEs">>} +]). +-else. +-define(RAW_HEADERS, [ + {<<"Dnt">>, <<"1">>}, + {<<"Connection">>, <<"close">>}, + {<<"X-Very-Camel-Case">>, <<"yEs">>} +]). +-endif. +-else. +-define(RAW_HEADERS, [ + {<<"Dnt">>, <<"1">>}, + {<<"Connection">>, <<"close">>}, + {<<"X-Very-Camel-Case">>, <<"yEs">>} +]). +-endif. + +-ifdef(OTP_RELEASE). +-if(?OTP_RELEASE >= 23). +-define(TLS_EXT, #{ + alpn := _, + client_hello_versions := _, + cookie := _, + ec_point_formats := _, + elliptic_curves := _, + key_share := _, + pre_shared_key := _, + psk_key_exchange_modes := _, + signature_algs := _, + signature_algs_cert := _, + sni := _ +}). +-else. +-define(TLS_EXT, #{ + alpn := _, + ec_point_formats := _, + elliptic_curves := _, + renegotiation_info := _, + signature_algs := _, + sni := _, + srp := _ +}). +-endif. +-endif. + +-endif.