From 037b286aa85acaaf439011bd7d2ae38685ce2f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 21 Nov 2018 17:39:12 +0100 Subject: Move many old HTTP test cases to the rest_handler test suite A bug was fixed in cowboy_rest where when content_types_provided returned a media type with a wildcard as first in the list, and a request comes in without an accept header, then the media_type value in the Req object would contain '*' instead of [] for the parameters. --- test/handlers/accept_callback_missing_h.erl | 15 +++ test/handlers/content_types_accepted_h.erl | 9 +- test/handlers/content_types_provided_h.erl | 24 ++++ test/handlers/expires_h.erl | 28 ++++ test/handlers/last_modified_h.erl | 24 ++++ test/old_http_SUITE.erl | 137 ------------------- test/rest_handler_SUITE.erl | 198 +++++++++++++++++++++++++++- 7 files changed, 291 insertions(+), 144 deletions(-) create mode 100644 test/handlers/accept_callback_missing_h.erl create mode 100644 test/handlers/content_types_provided_h.erl create mode 100644 test/handlers/expires_h.erl create mode 100644 test/handlers/last_modified_h.erl (limited to 'test') diff --git a/test/handlers/accept_callback_missing_h.erl b/test/handlers/accept_callback_missing_h.erl new file mode 100644 index 0000000..c97f123 --- /dev/null +++ b/test/handlers/accept_callback_missing_h.erl @@ -0,0 +1,15 @@ +-module(accept_callback_missing_h). + +-export([init/2]). +-export([allowed_methods/2]). +-export([content_types_accepted/2]). + +init(Req, State) -> + {cowboy_rest, Req, State}. + +allowed_methods(Req, State) -> + {[<<"PUT">>], Req, State}. + +content_types_accepted(Req, State) -> + ct_helper_error_h:ignore(cowboy_rest, process_content_type, 3), + {[{<<"text/plain">>, accept}], Req, State}. diff --git a/test/handlers/content_types_accepted_h.erl b/test/handlers/content_types_accepted_h.erl index 7aec1bb..b871dc8 100644 --- a/test/handlers/content_types_accepted_h.erl +++ b/test/handlers/content_types_accepted_h.erl @@ -7,6 +7,7 @@ -export([allowed_methods/2]). -export([content_types_accepted/2]). -export([put_multipart_mixed/2]). +-export([put_text_plain/2]). init(Req, Opts) -> {cowboy_rest, Req, Opts}. @@ -17,7 +18,13 @@ allowed_methods(Req, State) -> content_types_accepted(Req=#{qs := <<"multipart">>}, State) -> {[ {{<<"multipart">>, <<"mixed">>, [{<<"v">>, <<"1">>}]}, put_multipart_mixed} - ], Req, State}. + ], Req, State}; +content_types_accepted(Req=#{qs := <<"wildcard-param">>}, State) -> + {[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}. put_multipart_mixed(Req, State) -> {true, Req, State}. + +put_text_plain(Req0, State) -> + {ok, _, Req} = cowboy_req:read_body(Req0), + {true, Req, State}. diff --git a/test/handlers/content_types_provided_h.erl b/test/handlers/content_types_provided_h.erl new file mode 100644 index 0000000..5220c19 --- /dev/null +++ b/test/handlers/content_types_provided_h.erl @@ -0,0 +1,24 @@ +%% This module has different content_types_provided values +%% and/or sends a different response body depending on the +%% query string. + +-module(content_types_provided_h). + +-export([init/2]). +-export([content_types_provided/2]). +-export([get_text_plain/2]). + +init(Req, Opts) -> + {cowboy_rest, Req, Opts}. + +content_types_provided(Req=#{qs := <<"wildcard-param">>}, State) -> + {[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}. + +get_text_plain(Req=#{qs := <<"wildcard-param">>}, State) -> + {_, _, Param} = maps:get(media_type, Req), + Body = if + Param =:= [] -> <<"[]">>; + Param =/= [] -> + iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param]) + end, + {Body, Req, State}. diff --git a/test/handlers/expires_h.erl b/test/handlers/expires_h.erl new file mode 100644 index 0000000..87fcb14 --- /dev/null +++ b/test/handlers/expires_h.erl @@ -0,0 +1,28 @@ +%% This module sends a different expires value +%% depending on the query string. + +-module(expires_h). + +-export([init/2]). +-export([content_types_provided/2]). +-export([get_text_plain/2]). +-export([expires/2]). + +init(Req, Opts) -> + {cowboy_rest, Req, Opts}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. + +get_text_plain(Req, State) -> + {<<"This is REST!">>, Req, State}. + +expires(Req=#{qs := <<"tuple">>}, State) -> + {{{2012, 9, 21}, {22, 36, 14}}, Req, State}; +expires(Req=#{qs := <<"binary">>}, State) -> + {<<"0">>, Req, State}; +expires(Req=#{qs := <<"undefined">>}, State) -> + {undefined, Req, State}; +%% Simulate the callback being missing in other cases. +expires(#{qs := <<"missing">>}, _) -> + no_call. diff --git a/test/handlers/last_modified_h.erl b/test/handlers/last_modified_h.erl new file mode 100644 index 0000000..82893b3 --- /dev/null +++ b/test/handlers/last_modified_h.erl @@ -0,0 +1,24 @@ +%% This module sends a different last-modified value +%% depending on the query string. + +-module(last_modified_h). + +-export([init/2]). +-export([content_types_provided/2]). +-export([get_text_plain/2]). +-export([last_modified/2]). + +init(Req, Opts) -> + {cowboy_rest, Req, Opts}. + +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. + +get_text_plain(Req, State) -> + {<<"This is REST!">>, Req, State}. + +last_modified(Req=#{qs := <<"tuple">>}, State) -> + {{{2012, 9, 21}, {22, 36, 14}}, Req, State}; +%% Simulate the callback being missing in other cases. +last_modified(#{qs := <<"missing">>}, _) -> + no_call. diff --git a/test/old_http_SUITE.erl b/test/old_http_SUITE.erl index 6dbd2d6..81bb36c 100644 --- a/test/old_http_SUITE.erl +++ b/test/old_http_SUITE.erl @@ -100,70 +100,8 @@ init_dispatch(_) -> ]} ]). -%% Convenience functions. - -do_get(Path, Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, Path), - {response, _, Status, _} = gun:await(ConnPid, Ref), - gun:close(ConnPid), - Status. - %% Tests. -check_status(Config) -> - Tests = [ - {200, "/simple"} - ], - _ = [{Status, URL} = begin - Ret = do_get(URL, Config), - {Ret, URL} - end || {Status, URL} <- Tests]. - -rest_param_all(Config) -> - ConnPid = gun_open(Config), - %% Accept without param. - Ref1 = gun:get(ConnPid, "/param_all", - [{<<"accept">>, <<"text/plain">>}]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref1), - {ok, <<"[]">>} = gun:await_body(ConnPid, Ref1), - %% Accept with param. - Ref2 = gun:get(ConnPid, "/param_all", - [{<<"accept">>, <<"text/plain;level=1">>}]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref2), - {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref2), - %% Accept with param and quality. - Ref3 = gun:get(ConnPid, "/param_all", - [{<<"accept">>, <<"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5">>}]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref3), - {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref3), - Ref4 = gun:get(ConnPid, "/param_all", - [{<<"accept">>, <<"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8">>}]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref4), - {ok, <<"level=2">>} = gun:await_body(ConnPid, Ref4), - %% Without Accept. - Ref5 = gun:get(ConnPid, "/param_all"), - {response, nofin, 200, _} = gun:await(ConnPid, Ref5), - {ok, <<"'*'">>} = gun:await_body(ConnPid, Ref5), - %% Content-Type without param. - Ref6 = gun:put(ConnPid, "/param_all", - [{<<"content-type">>, <<"text/plain">>}]), - gun:data(ConnPid, Ref6, fin, "Hello world!"), - {response, fin, 204, _} = gun:await(ConnPid, Ref6), - %% Content-Type with param. - Ref7 = gun:put(ConnPid, "/param_all", - [{<<"content-type">>, <<"text/plain; charset=utf-8">>}]), - gun:data(ConnPid, Ref7, fin, "Hello world!"), - {response, fin, 204, _} = gun:await(ConnPid, Ref7), - ok. - -rest_bad_accept(Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/bad_accept", - [{<<"accept">>, <<"1">>}]), - {response, fin, 400, _} = gun:await(ConnPid, Ref), - ok. - rest_bad_content_type(Config) -> ConnPid = gun_open(Config), Ref = gun:patch(ConnPid, "/bad_content_type", @@ -171,87 +109,12 @@ rest_bad_content_type(Config) -> {response, fin, 415, _} = gun:await(ConnPid, Ref), ok. -rest_expires(Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/rest_expires"), - {response, nofin, 200, Headers} = gun:await(ConnPid, Ref), - {_, Expires} = lists:keyfind(<<"expires">>, 1, Headers), - {_, LastModified} = lists:keyfind(<<"last-modified">>, 1, Headers), - Expires = LastModified = <<"Fri, 21 Sep 2012 22:36:14 GMT">>, - ok. - -rest_expires_binary(Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/rest_expires_binary"), - {response, nofin, 200, Headers} = gun:await(ConnPid, Ref), - {_, <<"0">>} = lists:keyfind(<<"expires">>, 1, Headers), - ok. - -rest_last_modified_undefined(Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/simple", - [{<<"if-modified-since">>, <<"Fri, 21 Sep 2012 22:36:14 GMT">>}]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref), - ok. - -rest_keepalive(Config) -> - ConnPid = gun_open(Config), - Refs = [gun:get(ConnPid, "/simple") || _ <- lists:seq(1, 10)], - _ = [begin - {response, nofin, 200, Headers} = gun:await(ConnPid, Ref), - false = lists:keymember(<<"connection">>, 1, Headers) - end || Ref <- Refs], - ok. - -rest_keepalive_post(Config) -> - ConnPid = gun_open(Config), - Refs = [begin - Ref1 = gun:post(ConnPid, "/forbidden_post", [ - {<<"content-type">>, <<"text/plain">>}, - {<<"content-length">>, <<"12">>} - ]), - gun:data(ConnPid, Ref1, fin, "Hello world!"), - Ref2 = gun:post(ConnPid, "/simple_post", [ - {<<"content-type">>, <<"text/plain">>}, - {<<"content-length">>, <<"12">>} - ]), - gun:data(ConnPid, Ref2, fin, "Hello world!"), - {Ref1, Ref2} - end || _ <- lists:seq(1, 5)], - _ = [begin - {response, fin, 403, Headers1} = gun:await(ConnPid, Ref1), - false = lists:keymember(<<"connection">>, 1, Headers1), - {response, fin, 303, Headers2} = gun:await(ConnPid, Ref2), - false = lists:keymember(<<"connection">>, 1, Headers2) - end || {Ref1, Ref2} <- Refs], - ok. - -rest_missing_get_callbacks(Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/missing_get_callbacks"), - {response, fin, 500, _} = gun:await(ConnPid, Ref), - ok. - -rest_missing_put_callbacks(Config) -> - ConnPid = gun_open(Config), - Ref = gun:put(ConnPid, "/missing_put_callbacks", - [{<<"content-type">>, <<"application/json">>}], <<"{}">>), - {response, fin, 500, _} = gun:await(ConnPid, Ref), - ok. - rest_nodelete(Config) -> ConnPid = gun_open(Config), Ref = gun:delete(ConnPid, "/nodelete"), {response, fin, 500, _} = gun:await(ConnPid, Ref), ok. -rest_options_default(Config) -> - ConnPid = gun_open(Config), - Ref = gun:options(ConnPid, "/rest_empty_resource"), - {response, fin, 200, Headers} = gun:await(ConnPid, Ref), - {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers), - ok. - rest_patch(Config) -> Tests = [ {204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>}, diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index 88d3554..b21dda6 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -39,6 +39,7 @@ end_per_group(Name, _) -> init_dispatch(_) -> cowboy_router:compile([{'_', [ {"/", rest_hello_h, []}, + {"/accept_callback_missing", accept_callback_missing_h, []}, {"/charsets_provided", charsets_provided_h, []}, {"/charsets_provided_empty", charsets_provided_empty_h, []}, {"/charset_in_content_types_provided", @@ -48,7 +49,10 @@ init_dispatch(_) -> {"/charset_in_content_types_provided_implicit_no_callback", charset_in_content_types_provided_implicit_no_callback_h, []}, {"/content_types_accepted", content_types_accepted_h, []}, + {"/content_types_provided", content_types_provided_h, []}, + {"/expires", expires_h, []}, {"/if_range", if_range_h, []}, + {"/last_modified", last_modified_h, []}, {"/provide_callback_missing", provide_callback_missing_h, []}, {"/provide_range_callback", provide_range_callback_h, []}, {"/range_satisfiable", range_satisfiable_h, []}, @@ -70,6 +74,16 @@ do_decode(Headers, Body) -> %% Tests. +accept_callback_missing(Config) -> + doc("A 500 response must be sent when the AcceptCallback can't be called."), + ConnPid = gun_open(Config), + Ref = gun:put(ConnPid, "/accept_callback_missing", [ + {<<"accept-encoding">>, <<"gzip">>}, + {<<"content-type">>, <<"text/plain">>} + ], <<"Missing!">>), + {response, fin, 500, _} = gun:await(ConnPid, Ref), + ok. + charset_in_content_types_provided(Config) -> doc("When a charset is matched explictly in content_types_provided, " "that charset is used and the charsets_provided callback is ignored."), @@ -281,26 +295,154 @@ content_types_accepted_ignore_multipart_boundary(Config) -> {response, _, 204, _} = gun:await(ConnPid, Ref), ok. -error_on_malformed_if_match(Config) -> - doc("A malformed If-Match header must result in a 400 response."), +content_types_accepted_wildcard_param_no_content_type_param(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_accepted callback, a content-type header " + "with no parameters must be accepted."), ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, "/", [ + Ref = gun:put(ConnPid, "/content_types_accepted?wildcard-param", [ {<<"accept-encoding">>, <<"gzip">>}, - {<<"if-match">>, <<"bad">>} + {<<"content-type">>, <<"text/plain">>} ]), - {response, _, 400, _} = gun:await(ConnPid, Ref), + gun:data(ConnPid, Ref, fin, "Hello world!"), + {response, fin, 204, _} = gun:await(ConnPid, Ref), + ok. + +content_types_accepted_wildcard_param_content_type_with_param(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_accepted callback, a content-type header " + "with a parameter must be accepted."), + ConnPid = gun_open(Config), + Ref = gun:put(ConnPid, "/content_types_accepted?wildcard-param", [ + {<<"accept-encoding">>, <<"gzip">>}, + {<<"content-type">>, <<"text/plain; charset=utf-8">>} + ]), + gun:data(ConnPid, Ref, fin, "Hello world!"), + {response, fin, 204, _} = gun:await(ConnPid, Ref), + ok. + +content_types_provided_wildcard_param_no_accept_param(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_provided callback, an accept header " + "with no parameters must be accepted."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/content_types_provided?wildcard-param", [ + {<<"accept">>, <<"text/plain">>}, + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref), + {ok, <<"[]">>} = gun:await_body(ConnPid, Ref), + ok. + +content_types_provided_wildcard_param_accept_with_param(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_provided callback, an accept header " + "with a parameter must be accepted."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/content_types_provided?wildcard-param", [ + {<<"accept">>, <<"text/plain;level=1">>}, + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref), + {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref), + ok. + +content_types_provided_wildcard_param_accept_with_param_and_qvalue(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_provided callback, an accept header " + "with two media types containing parameters including a " + "q-value must be accepted. The q-value determines which."), + ConnPid = gun_open(Config), + Ref1 = gun:get(ConnPid, "/content_types_provided?wildcard-param", [ + {<<"accept">>, <<"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5">>}, + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref1), + {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref1), + Ref2 = gun:get(ConnPid, "/content_types_provided?wildcard-param", [ + {<<"accept">>, <<"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8">>}, + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref2), + {ok, <<"level=2">>} = gun:await_body(ConnPid, Ref2), ok. +content_types_provided_wildcard_param_no_accept_header(Config) -> + doc("When a wildcard is returned for parameters from the " + "content_types_provided callback, the lack of accept header " + "results in the first media type returned being accepted. " + "The wildcard must however not be present in the media_type " + "value added to the Req object."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/content_types_provided?wildcard-param", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, _} = gun:await(ConnPid, Ref), + {ok, <<"[]">>} = gun:await_body(ConnPid, Ref), + ok. + +error_on_malformed_accept(Config) -> + doc("A malformed Accept header must result in a 400 response."), + do_error_on_malformed_header(Config, <<"accept">>). + +error_on_malformed_if_match(Config) -> + doc("A malformed If-Match header must result in a 400 response."), + do_error_on_malformed_header(Config, <<"if-match">>). + error_on_malformed_if_none_match(Config) -> doc("A malformed If-None-Match header must result in a 400 response."), + do_error_on_malformed_header(Config, <<"if-none-match">>). + +do_error_on_malformed_header(Config, Name) -> ConnPid = gun_open(Config), Ref = gun:get(ConnPid, "/", [ {<<"accept-encoding">>, <<"gzip">>}, - {<<"if-none-match">>, <<"bad">>} + {Name, <<"bad">>} ]), {response, _, 400, _} = gun:await(ConnPid, Ref), ok. +expires_binary(Config) -> + doc("The expires header can also be given as a binary " + "to indicate a date in the past. (RFC7234 5.3)"), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/expires?binary", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"0">>} = lists:keyfind(<<"expires">>, 1, Headers), + ok. + +expires_missing(Config) -> + doc("The expires header must not be sent when the callback is not exported."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/expires?missing", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + false = lists:keyfind(<<"expires">>, 1, Headers), + ok. + +expires_tuple(Config) -> + doc("The expires header can be given as a date tuple. (RFC7234 5.3)"), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/expires?tuple", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"Fri, 21 Sep 2012 22:36:14 GMT">>} = lists:keyfind(<<"expires">>, 1, Headers), + ok. + +expires_undefined(Config) -> + doc("The expires header must not be sent when undefined is returned."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/expires?undefined", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + false = lists:keyfind(<<"expires">>, 1, Headers), + ok. + if_range_etag_equal(Config) -> doc("When the if-range header matches, a 206 partial content " "response is expected for an otherwise valid range request. (RFC7233 3.2)"), @@ -405,6 +547,50 @@ if_range_date_not_equal(Config) -> false = lists:keyfind(<<"content-range">>, 1, Headers), ok. +last_modified(Config) -> + doc("The last-modified header can be given as a date tuple. (RFC7232 2.2)"), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/last_modified?tuple", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"Fri, 21 Sep 2012 22:36:14 GMT">>} = lists:keyfind(<<"last-modified">>, 1, Headers), + ok. + +last_modified_missing(Config) -> + doc("The last-modified header must not be sent when the callback is not exported."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/last_modified?missing", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, _, 200, Headers} = gun:await(ConnPid, Ref), + false = lists:keyfind(<<"last-modified">>, 1, Headers), + ok. + +options_missing(Config) -> + doc("A successful OPTIONS request to a simple handler results in " + "a 200 OK response with the allow header set. (RFC7231 4.3.7)"), + ConnPid = gun_open(Config), + Ref = gun:options(ConnPid, "/", [ + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, fin, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers), + ok. + +provide_callback(Config) -> + doc("A successful GET request to a simple handler results in " + "a 200 OK response with the content-type set. (RFC7231 4.3.1, RFC7231 6.3.1)"), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/", [ + {<<"accept">>, <<"*/*">>}, + {<<"accept-encoding">>, <<"gzip">>} + ]), + {response, nofin, 200, Headers} = gun:await(ConnPid, Ref), + {_, <<"text/plain">>} = lists:keyfind(<<"content-type">>, 1, Headers), + {ok, <<"This is REST!">>} = gun:await_body(ConnPid, Ref), + ok. + provide_callback_missing(Config) -> doc("A 500 response must be sent when the ProvideCallback can't be called."), ConnPid = gun_open(Config), -- cgit v1.2.3