0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Move the final old HTTP suite tests and remove it

This commit is contained in:
Loïc Hoguin 2018-11-22 00:12:18 +01:00
parent 037b286aa8
commit 0223f69fcd
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
24 changed files with 241 additions and 591 deletions

View file

@ -0,0 +1,23 @@
%% This module returns something different in
%% AcceptCallback depending on the query string.
-module(accept_callback_h).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([put_text_plain/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"PUT">>, <<"POST">>, <<"PATCH">>], Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, put_text_plain}], Req, State}.
put_text_plain(Req=#{qs := <<"false">>}, State) ->
{false, Req, State};
put_text_plain(Req=#{qs := <<"true">>}, State) ->
{true, Req, State}.

View file

@ -1,5 +1,5 @@
%% This module accepts a multipart media type with parameters
%% that do not include boundary.
%% This module returns something different in
%% content_types_accepted depending on the query string.
-module(content_types_accepted_h).
@ -19,6 +19,8 @@ content_types_accepted(Req=#{qs := <<"multipart">>}, State) ->
{[
{{<<"multipart">>, <<"mixed">>, [{<<"v">>, <<"1">>}]}, put_multipart_mixed}
], Req, State};
content_types_accepted(Req=#{qs := <<"param">>}, State) ->
{[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]}, put_text_plain}], Req, State};
content_types_accepted(Req=#{qs := <<"wildcard-param">>}, State) ->
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.

View file

@ -0,0 +1,17 @@
%% This module accepts a multipart media type with parameters
%% that do not include boundary.
-module(delete_resource_h).
-export([init/2]).
-export([allowed_methods/2]).
-export([delete_resource/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"DELETE">>], Req, State}.
delete_resource(#{qs := <<"missing">>}, _) ->
no_call.

View file

@ -0,0 +1,39 @@
%% This module sends a different etag value
%% depending on the query string.
-module(generate_etag_h).
-export([init/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
-export([generate_etag/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}.
%% Correct return values from generate_etag/2.
generate_etag(Req=#{qs := <<"tuple-weak">>}, State) ->
{{weak, <<"etag-header-value">>}, Req, State};
generate_etag(Req=#{qs := <<"tuple-strong">>}, State) ->
{{strong, <<"etag-header-value">>}, Req, State};
%% Backwards compatible return values from generate_etag/2.
generate_etag(Req=#{qs := <<"binary-weak-quoted">>}, State) ->
{<<"W/\"etag-header-value\"">>, Req, State};
generate_etag(Req=#{qs := <<"binary-strong-quoted">>}, State) ->
{<<"\"etag-header-value\"">>, Req, State};
%% Invalid return values from generate_etag/2.
generate_etag(Req=#{qs := <<"binary-weak-unquoted">>}, State) ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"W/etag-header-value">>, Req, State};
generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"etag-header-value">>, Req, State};
%% Simulate the callback being missing in other cases.
generate_etag(#{qs := <<"missing">>}, _) ->
no_call.

View file

@ -1,183 +0,0 @@
%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(old_http_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-import(ct_helper, [config/2]).
-import(cowboy_test, [gun_open/1]).
-import(cowboy_test, [gun_open/2]).
-import(cowboy_test, [gun_down/1]).
-import(cowboy_test, [raw_open/1]).
-import(cowboy_test, [raw_send/2]).
-import(cowboy_test, [raw_recv_head/1]).
-import(cowboy_test, [raw_expect_recv/2]).
%% ct.
all() ->
[
{group, http},
{group, https},
{group, http_compress},
{group, https_compress}
].
groups() ->
Tests = ct_helper:all(?MODULE),
[
{http, [], Tests}, %% @todo parallel
{https, [parallel], Tests},
{http_compress, [parallel], Tests},
{https_compress, [parallel], Tests}
].
init_per_group(Name = http, Config) ->
cowboy_test:init_http(Name, #{env => #{dispatch => init_dispatch(Config)}}, Config);
init_per_group(Name = https, Config) ->
cowboy_test:init_https(Name, #{env => #{dispatch => init_dispatch(Config)}}, Config);
init_per_group(Name = http_compress, Config) ->
cowboy_test:init_http(Name, #{
env => #{dispatch => init_dispatch(Config)},
stream_handlers => [cowboy_compress_h, cowboy_stream_h]
}, Config);
init_per_group(Name = https_compress, Config) ->
cowboy_test:init_https(Name, #{
env => #{dispatch => init_dispatch(Config)},
stream_handlers => [cowboy_compress_h, cowboy_stream_h]
}, Config).
end_per_group(Name, _) ->
ok = cowboy:stop_listener(Name).
%% Dispatch configuration.
init_dispatch(_) ->
cowboy_router:compile([
{"localhost", [
{"/chunked_response", http_chunked, []},
{"/headers/dupe", http_handler,
[{headers, #{<<"connection">> => <<"close">>}}]},
{"/set_resp/header", http_set_resp,
[{headers, #{<<"vary">> => <<"Accept">>}}]},
{"/set_resp/overwrite", http_set_resp,
[{headers, #{<<"server">> => <<"DesireDrive/1.0">>}}]},
{"/set_resp/body", http_set_resp,
[{body, <<"A flameless dance does not equal a cycle">>}]},
{"/handler_errors", http_errors, []},
{"/echo/body", http_echo_body, []},
{"/param_all", rest_param_all, []},
{"/bad_accept", rest_simple_resource, []},
{"/bad_content_type", rest_patch_resource, []},
{"/simple", rest_simple_resource, []},
{"/forbidden_post", rest_forbidden_resource, [true]},
{"/simple_post", rest_forbidden_resource, [false]},
{"/missing_get_callbacks", rest_missing_callbacks, []},
{"/missing_put_callbacks", rest_missing_callbacks, []},
{"/nodelete", rest_nodelete_resource, []},
{"/post_charset", rest_post_charset_resource, []},
{"/postonly", rest_postonly_resource, []},
{"/patch", rest_patch_resource, []},
{"/resetags", rest_resource_etags, []},
{"/rest_expires", rest_expires, []},
{"/rest_expires_binary", rest_expires_binary, []},
{"/rest_empty_resource", rest_empty_resource, []},
{"/loop_stream_recv", http_loop_stream_recv, []},
{"/", http_handler, []}
]}
]).
%% Tests.
rest_bad_content_type(Config) ->
ConnPid = gun_open(Config),
Ref = gun:patch(ConnPid, "/bad_content_type",
[{<<"content-type">>, <<"text/plain, text/html">>}], <<"Whatever">>),
{response, fin, 415, _} = 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_patch(Config) ->
Tests = [
{204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>},
{400, [{<<"content-type">>, <<"text/plain">>}], <<"false">>},
{400, [{<<"content-type">>, <<"text/plain">>}], <<"stop">>},
{415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>}
],
ConnPid = gun_open(Config),
_ = [begin
Ref = gun:patch(ConnPid, "/patch", Headers, Body),
{response, fin, Status, _} = gun:await(ConnPid, Ref)
end || {Status, Headers, Body} <- Tests],
ok.
rest_post_charset(Config) ->
ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/post_charset",
[{<<"content-type">>, <<"text/plain;charset=UTF-8">>}], "12345"),
{response, fin, 204, _} = gun:await(ConnPid, Ref),
ok.
rest_postonly(Config) ->
ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/postonly",
[{<<"content-type">>, <<"text/plain">>}], "12345"),
{response, fin, 204, _} = gun:await(ConnPid, Ref),
ok.
rest_resource_get_etag(Config, Type) ->
rest_resource_get_etag(Config, Type, []).
rest_resource_get_etag(Config, Type, Headers) ->
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/resetags?type=" ++ Type, Headers),
{response, _, Status, RespHeaders} = gun:await(ConnPid, Ref),
case lists:keyfind(<<"etag">>, 1, RespHeaders) of
false -> {Status, false};
{<<"etag">>, ETag} -> {Status, ETag}
end.
rest_resource_etags(Config) ->
Tests = [
{200, <<"W/\"etag-header-value\"">>, "tuple-weak"},
{200, <<"\"etag-header-value\"">>, "tuple-strong"},
{200, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
{200, <<"\"etag-header-value\"">>, "binary-strong-quoted"},
{500, false, "binary-strong-unquoted"},
{500, false, "binary-weak-unquoted"}
],
_ = [{Status, ETag, Type} = begin
{Ret, RespETag} = rest_resource_get_etag(Config, Type),
{Ret, RespETag, Type}
end || {Status, ETag, Type} <- Tests].
rest_resource_etags_if_none_match(Config) ->
Tests = [
{304, <<"W/\"etag-header-value\"">>, "tuple-weak"},
{304, <<"\"etag-header-value\"">>, "tuple-strong"},
{304, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
{304, <<"\"etag-header-value\"">>, "binary-strong-quoted"}
],
_ = [{Status, Type} = begin
{Ret, _} = rest_resource_get_etag(Config, Type,
[{<<"if-none-match">>, ETag}]),
{Ret, Type}
end || {Status, ETag, Type} <- Tests].

View file

@ -1,15 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_chunked).
-export([init/2]).
init(Req, Opts) ->
Req2 = cowboy_req:stream_reply(200, Req),
%% Try an empty chunk to make sure the stream doesn't get closed.
cowboy_req:stream_body([<<>>], nofin, Req2),
timer:sleep(100),
cowboy_req:stream_body("chunked_handler\r\n", nofin, Req2),
timer:sleep(100),
cowboy_req:stream_body("works fine!", fin, Req2),
{ok, Req2, Opts}.

View file

@ -1,21 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_echo_body).
-export([init/2]).
init(Req, Opts) ->
true = cowboy_req:has_body(Req),
Req3 = case cowboy_req:read_body(Req, #{length => 1000000}) of
{ok, Body, Req2} -> handle_body(Req2, Body);
{more, _, Req2} -> handle_badlength(Req2)
end,
{ok, Req3, Opts}.
handle_badlength(Req) ->
cowboy_req:reply(413, #{}, <<"Request entity too large">>, Req).
handle_body(Req, Body) ->
Size = cowboy_req:body_length(Req),
Size = byte_size(Body),
cowboy_req:reply(200, #{}, Body, Req).

View file

@ -1,19 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_errors).
-export([init/2]).
-spec init(_, _) -> no_return().
init(Req, _Opts) ->
#{'case' := Case} = cowboy_req:match_qs(['case'], Req),
case_init(Case, Req).
-spec case_init(_, _) -> no_return().
case_init(<<"init_before_reply">> = Case, _Req) ->
ct_helper_error_h:ignore(?MODULE, case_init, 2),
error(Case);
case_init(<<"init_after_reply">> = Case, Req) ->
ct_helper_error_h:ignore(?MODULE, case_init, 2),
_ = cowboy_req:reply(200, #{}, "http_handler_crashes", Req),
error(Case).

View file

@ -1,10 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_handler).
-export([init/2]).
init(Req, Opts) ->
Headers = proplists:get_value(headers, Opts, #{}),
Body = proplists:get_value(body, Opts, "http_handler"),
{ok, cowboy_req:reply(200, Headers, Body, Req), Opts}.

View file

@ -1,34 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_loop_stream_recv).
-export([init/2]).
-export([info/3]).
-export([terminate/3]).
init(Req, _) ->
receive after 100 -> ok end,
self() ! stream,
{cowboy_loop, Req, undefined}.
info(stream, Req, undefined) ->
stream(Req, 1, <<>>).
stream(Req, ID, Acc) ->
case cowboy_req:read_body(Req) of
{ok, <<>>, Req2} ->
{stop, cowboy_req:reply(200, Req2), undefined};
{_, Data, Req2} ->
parse_id(Req2, ID, << Acc/binary, Data/binary >>)
end.
parse_id(Req, ID, Data) ->
case Data of
<< ID:32, Rest/bits >> ->
parse_id(Req, ID + 1, Rest);
_ ->
stream(Req, ID, Data)
end.
terminate(stop, _, _) ->
ok.

View file

@ -1,25 +0,0 @@
%% Feel free to use, reuse and abuse the code in this file.
-module(http_set_resp).
-export([init/2]).
init(Req, Opts) ->
Headers = proplists:get_value(headers, Opts, #{}),
Body = proplists:get_value(body, Opts, <<"http_handler_set_resp">>),
Req2 = lists:foldl(fun({Name, Value}, R) ->
cowboy_req:set_resp_header(Name, Value, R)
end, Req, maps:to_list(Headers)),
Req3 = cowboy_req:set_resp_body(Body, Req2),
Req4 = cowboy_req:set_resp_header(<<"x-cowboy-test">>, <<"ok">>, Req3),
Req5 = cowboy_req:set_resp_cookie(<<"cake">>, <<"lie">>, Req4),
case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req5) of
false -> {ok, Req5, Opts};
true ->
case cowboy_req:has_resp_body(Req5) of
false ->
{ok, Req5, Opts};
true ->
{ok, cowboy_req:reply(200, Req5), Opts}
end
end.

View file

@ -1,6 +0,0 @@
-module(rest_empty_resource).
-export([init/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.

View file

@ -1,22 +0,0 @@
-module(rest_expires).
-export([init/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
-export([expires/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}.
expires(Req, State) ->
{{{2012, 9, 21}, {22, 36, 14}}, Req, State}.
last_modified(Req, State) ->
{{{2012, 9, 21}, {22, 36, 14}}, Req, State}.

View file

@ -1,18 +0,0 @@
-module(rest_expires_binary).
-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, State) ->
{<<"0">>, Req, State}.

View file

@ -1,32 +0,0 @@
-module(rest_forbidden_resource).
-export([init/2]).
-export([allowed_methods/2]).
-export([forbidden/2]).
-export([content_types_provided/2]).
-export([content_types_accepted/2]).
-export([to_text/2]).
-export([from_text/2]).
init(Req, [Forbidden]) ->
{cowboy_rest, Req, Forbidden}.
allowed_methods(Req, State) ->
{[<<"GET">>, <<"HEAD">>, <<"POST">>], Req, State}.
forbidden(Req, State=true) ->
{true, Req, State};
forbidden(Req, State=false) ->
{false, Req, State}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, to_text}], Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, from_text}], Req, State}.
to_text(Req, State) ->
{<<"This is REST!">>, Req, State}.
from_text(Req, State) ->
{{true, cowboy_req:path(Req)}, Req, State}.

View file

@ -1,24 +0,0 @@
-module(rest_missing_callbacks).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"GET">>, <<"PUT">>], Req, State}.
content_types_accepted(Req, State) ->
ct_helper_error_h:ignore(cowboy_rest, process_content_type, 3),
{[
{<<"application/json">>, put_application_json}
], Req, State}.
content_types_provided(Req, State) ->
ct_helper_error_h:ignore(cowboy_rest, set_resp_body, 2),
{[
{<<"text/plain">>, get_text_plain}
], Req, State}.

View file

@ -1,18 +0,0 @@
-module(rest_nodelete_resource).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"GET">>, <<"HEAD">>, <<"DELETE">>], Req, State}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
get_text_plain(Req, State) ->
{<<"This is REST!">>, Req, State}.

View file

@ -1,36 +0,0 @@
-module(rest_param_all).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
-export([content_types_accepted/2]).
-export([put_text_plain/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"GET">>, <<"PUT">>], Req, State}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}.
get_text_plain(Req, State) ->
{_, _, Param} = maps:get(media_type, Req, {<<"text">>, <<"plain">>, []}),
Body = if
Param == '*' ->
<<"'*'">>;
Param == [] ->
<<"[]">>;
Param /= [] ->
iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param])
end,
{Body, Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.
put_text_plain(Req0, State) ->
{ok, _, Req} = cowboy_req:read_body(Req0),
{true, Req, State}.

View file

@ -1,38 +0,0 @@
-module(rest_patch_resource).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
-export([content_types_accepted/2]).
-export([patch_text_plain/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"HEAD">>, <<"GET">>, <<"PATCH">>], Req, State}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
get_text_plain(Req, State) ->
{<<"This is REST!">>, Req, State}.
content_types_accepted(Req, State) ->
case cowboy_req:method(Req) of
<<"PATCH">> ->
{[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req, State};
_ ->
{[], Req, State}
end.
patch_text_plain(Req, State) ->
case cowboy_req:read_body(Req) of
{ok, <<"stop">>, Req0} ->
{stop, cowboy_req:reply(400, Req0), State};
{ok, <<"false">>, Req0} ->
{false, Req0, State};
{ok, _Body, Req0} ->
{true, Req0, State}
end.

View file

@ -1,19 +0,0 @@
-module(rest_post_charset_resource).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([from_text/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"POST">>], Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]},
from_text}], Req, State}.
from_text(Req, State) ->
{true, Req, State}.

View file

@ -1,18 +0,0 @@
-module(rest_postonly_resource).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([from_text/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"POST">>], Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, '*'}, from_text}], Req, State}.
from_text(Req, State) ->
{true, Req, State}.

View file

@ -1,37 +0,0 @@
-module(rest_resource_etags).
-export([init/2]).
-export([generate_etag/2]).
-export([content_types_provided/2]).
-export([get_text_plain/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
generate_etag(Req, State) ->
#{type := Type} = cowboy_req:match_qs([type], Req),
case Type of
%% Correct return values from generate_etag/2.
<<"tuple-weak">> ->
{{weak, <<"etag-header-value">>}, Req, State};
<<"tuple-strong">> ->
{{strong, <<"etag-header-value">>}, Req, State};
%% Backwards compatible return values from generate_etag/2.
<<"binary-weak-quoted">> ->
{<<"W/\"etag-header-value\"">>, Req, State};
<<"binary-strong-quoted">> ->
{<<"\"etag-header-value\"">>, Req, State};
%% Invalid return values from generate_etag/2.
<<"binary-strong-unquoted">> ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"etag-header-value">>, Req, State};
<<"binary-weak-unquoted">> ->
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"W/etag-header-value">>, Req, State}
end.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
get_text_plain(Req, State) ->
{<<"This is REST!">>, Req, State}.

View file

@ -1,14 +0,0 @@
-module(rest_simple_resource).
-export([init/2]).
-export([content_types_provided/2]).
-export([get_text_plain/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}.

View file

@ -39,6 +39,7 @@ end_per_group(Name, _) ->
init_dispatch(_) ->
cowboy_router:compile([{'_', [
{"/", rest_hello_h, []},
{"/accept_callback", accept_callback_h, []},
{"/accept_callback_missing", accept_callback_missing_h, []},
{"/charsets_provided", charsets_provided_h, []},
{"/charsets_provided_empty", charsets_provided_empty_h, []},
@ -50,7 +51,9 @@ init_dispatch(_) ->
charset_in_content_types_provided_implicit_no_callback_h, []},
{"/content_types_accepted", content_types_accepted_h, []},
{"/content_types_provided", content_types_provided_h, []},
{"/delete_resource", delete_resource_h, []},
{"/expires", expires_h, []},
{"/generate_etag", generate_etag_h, []},
{"/if_range", if_range_h, []},
{"/last_modified", last_modified_h, []},
{"/provide_callback_missing", provide_callback_missing_h, []},
@ -84,6 +87,44 @@ accept_callback_missing(Config) ->
{response, fin, 500, _} = gun:await(ConnPid, Ref),
ok.
accept_callback_patch_false(Config) ->
do_accept_callback_false(Config, patch).
accept_callback_patch_true(Config) ->
do_accept_callback_true(Config, patch).
accept_callback_post_false(Config) ->
do_accept_callback_false(Config, post).
accept_callback_post_true(Config) ->
do_accept_callback_true(Config, post).
accept_callback_put_false(Config) ->
do_accept_callback_false(Config, put).
accept_callback_put_true(Config) ->
do_accept_callback_true(Config, put).
do_accept_callback_false(Config, Fun) ->
doc("When AcceptCallback returns false a 400 response must be returned."),
ConnPid = gun_open(Config),
Ref = gun:Fun(ConnPid, "/accept_callback?false", [
{<<"accept-encoding">>, <<"gzip">>},
{<<"content-type">>, <<"text/plain">>}
], <<"Request body.">>),
{response, _, 400, _} = gun:await(ConnPid, Ref),
ok.
do_accept_callback_true(Config, Fun) ->
doc("When AcceptCallback returns true a 204 response must be returned."),
ConnPid = gun_open(Config),
Ref = gun:Fun(ConnPid, "/accept_callback?true", [
{<<"accept-encoding">>, <<"gzip">>},
{<<"content-type">>, <<"text/plain">>}
], <<"Request body.">>),
{response, _, 204, _} = 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."),
@ -282,6 +323,17 @@ charsets_provided_empty_noheader(Config) ->
{response, _, 406, _} = gun:await(ConnPid, Ref),
ok.
content_type_invalid(Config) ->
doc("An invalid content-type in a POST/PATCH/PUT request "
"must be rejected with a 415 unsupported media type response. (RFC7231 6.5.13)"),
ConnPid = gun_open(Config),
Ref = gun:put(ConnPid, "/content_types_accepted?wildcard-param", [
{<<"accept-encoding">>, <<"gzip">>},
{<<"content-type">>, <<"text/plain, text/html">>}
]),
{response, fin, 415, _} = gun:await(ConnPid, Ref),
ok.
content_types_accepted_ignore_multipart_boundary(Config) ->
doc("When a multipart content-type is provided for the request "
"body, the boundary parameter is not expected to be returned "
@ -295,6 +347,18 @@ content_types_accepted_ignore_multipart_boundary(Config) ->
{response, _, 204, _} = gun:await(ConnPid, Ref),
ok.
content_types_accepted_param(Config) ->
doc("When a parameter is returned from the content_types_accepted "
"callback, and the same parameter is found in the content-type "
"header, the negotiation succeeds and the request is processed."),
ConnPid = gun_open(Config),
Ref = gun:put(ConnPid, "/content_types_accepted?param", [
{<<"accept-encoding">>, <<"gzip">>},
{<<"content-type">>, <<"text/plain;charset=UTF-8">>}
], "12345"),
{response, fin, 204, _} = gun:await(ConnPid, Ref),
ok.
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 "
@ -381,6 +445,17 @@ content_types_provided_wildcard_param_no_accept_header(Config) ->
{ok, <<"[]">>} = gun:await_body(ConnPid, Ref),
ok.
delete_resource_missing(Config) ->
doc("When a resource accepts the DELETE method and the "
"delete_resource callback is not exported, the "
"resource is incorrect and a 500 response is expected."),
ConnPid = gun_open(Config),
Ref = gun:delete(ConnPid, "/delete_resource?missing", [
{<<"accept-encoding">>, <<"gzip">>}
]),
{response, _, 500, _} = gun:await(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">>).
@ -443,6 +518,89 @@ expires_undefined(Config) ->
false = lists:keyfind(<<"expires">>, 1, Headers),
ok.
generate_etag_missing(Config) ->
doc("The etag header must not be sent when "
"the generate_etag callback is not exported."),
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/generate_etag?missing", [
{<<"accept-encoding">>, <<"gzip">>}
]),
{response, _, 200, Headers} = gun:await(ConnPid, Ref),
false = lists:keyfind(<<"etag">>, 1, Headers),
ok.
generate_etag_binary_strong(Config) ->
doc("The etag header must be sent when the generate_etag "
"callback returns a strong binary. (RFC7232 2.3)"),
do_generate_etag(Config, "binary-strong-quoted",
[], 200, {<<"etag">>, <<"\"etag-header-value\"">>}).
generate_etag_binary_weak(Config) ->
doc("The etag header must be sent when the generate_etag "
"callback returns a weak binary. (RFC7232 2.3)"),
do_generate_etag(Config, "binary-weak-quoted",
[], 200, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
generate_etag_invalid_binary_strong_unquoted(Config) ->
doc("When Cowboy cannot parse the generate_etag callback's "
"return value, a 500 response is returned without the etag header."),
do_generate_etag(Config, "binary-strong-unquoted", [], 500, false).
generate_etag_invalid_binary_weak_unquoted(Config) ->
doc("When Cowboy cannot parse the generate_etag callback's "
"return value, a 500 response is returned without the etag header."),
do_generate_etag(Config, "binary-weak-unquoted", [], 500, false).
generate_etag_tuple_strong(Config) ->
doc("The etag header must be sent when the generate_etag "
"callback returns a strong tuple. (RFC7232 2.3)"),
do_generate_etag(Config, "tuple-strong",
[], 200, {<<"etag">>, <<"\"etag-header-value\"">>}).
generate_etag_tuple_weak(Config) ->
doc("The etag header must be sent when the generate_etag "
"callback returns a weak tuple. (RFC7232 2.3)"),
do_generate_etag(Config, "tuple-weak",
[], 200, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
if_none_match_binary_strong(Config) ->
doc("When the if-none-match header matches a strong etag, "
"a 304 not modified response is returned. (RFC7232 3.2)"),
do_generate_etag(Config, "binary-strong-quoted",
[{<<"if-none-match">>, <<"\"etag-header-value\"">>}],
304, {<<"etag">>, <<"\"etag-header-value\"">>}).
if_none_match_binary_weak(Config) ->
doc("When the if-none-match header matches a weak etag, "
"a 304 not modified response is returned. (RFC7232 3.2)"),
do_generate_etag(Config, "binary-weak-quoted",
[{<<"if-none-match">>, <<"W/\"etag-header-value\"">>}],
304, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
if_none_match_tuple_strong(Config) ->
doc("When the if-none-match header matches a strong etag, "
"a 304 not modified response is returned. (RFC7232 3.2)"),
do_generate_etag(Config, "tuple-strong",
[{<<"if-none-match">>, <<"\"etag-header-value\"">>}],
304, {<<"etag">>, <<"\"etag-header-value\"">>}).
if_none_match_tuple_weak(Config) ->
doc("When the if-none-match header matches a weak etag, "
"a 304 not modified response is returned. (RFC7232 3.2)"),
do_generate_etag(Config, "tuple-weak",
[{<<"if-none-match">>, <<"W/\"etag-header-value\"">>}],
304, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
do_generate_etag(Config, Qs, ReqHeaders, Status, Etag) ->
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/generate_etag?" ++ Qs, [
{<<"accept-encoding">>, <<"gzip">>}
|ReqHeaders
]),
{response, _, Status, RespHeaders} = gun:await(ConnPid, Ref),
Etag = lists:keyfind(<<"etag">>, 1, RespHeaders),
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)"),