-module(mochiweb_tests).

-include_lib("eunit/include/eunit.hrl").

-include("mochiweb_test_util.hrl").

with_server(Transport, ServerFun, ClientFun) ->
    mochiweb_test_util:with_server(Transport, ServerFun,
				   ClientFun).

request_test() ->
    R = mochiweb_request:new(z, z,
			     "//foo///bar/baz%20wibble+quux?qs=2", z, []),
    "/foo/bar/baz wibble quux" = mochiweb_request:get(path,
						      R),
    ok.

-define(LARGE_TIMEOUT, 60).

single_http_GET_test() -> do_GET(plain, 1).

single_https_GET_test() -> do_GET(ssl, 1).

multiple_http_GET_test() -> do_GET(plain, 3).

multiple_https_GET_test() -> do_GET(ssl, 3).

hundred_http_GET_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_GET(plain, 100))) end}.

hundred_https_GET_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_GET(ssl, 100))) end}.

single_128_http_POST_test() -> do_POST(plain, 128, 1).

single_128_https_POST_test() -> do_POST(ssl, 128, 1).

single_2k_http_POST_test() -> do_POST(plain, 2048, 1).

single_2k_https_POST_test() -> do_POST(ssl, 2048, 1).

single_100k_http_POST_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(plain, 102400, 1)))
     end}.

single_100k_https_POST_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(ssl, 102400, 1)))
     end}.

multiple_100k_http_POST_test() ->
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(plain, 102400, 3)))
     end}.

multiple_100K_https_POST_test() ->
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(ssl, 102400, 3)))
     end}.

hundred_128_http_POST_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(plain, 128, 100)))
     end}.

hundred_128_https_POST_test_() ->
    % note the underscore
    {timeout, ?LARGE_TIMEOUT,
     fun () -> ?assertEqual(ok, (do_POST(ssl, 128, 100)))
     end}.

single_GET_scheme_test_() ->
    [{"ssl", ?_assertEqual(ok, (do_GET("derp", ssl, 1)))},
     {"plain",
      ?_assertEqual(ok, (do_GET("derp", plain, 1)))}].

single_GET_absoluteURI_test_() ->
    Uri = "https://example.com:123/x/",
    ServerFun = fun (Req) ->
			mochiweb_request:ok({"text/plain",
					     mochiweb_request:get(path, Req)},
					    Req)
		end,
    %% Note that all the scheme/host/port information is discarded from path
    ClientFun = new_client_fun('GET',
			       [#treq{path = Uri, xreply = <<"/x/">>}]),
    [{atom_to_list(Transport),
      ?_assertEqual(ok,
		    (with_server(Transport, ServerFun, ClientFun)))}
     || Transport <- [ssl, plain]].

single_CONNECT_test_() ->
    [{"ssl", ?_assertEqual(ok, (do_CONNECT(ssl, 1)))},
     {"plain", ?_assertEqual(ok, (do_CONNECT(plain, 1)))}].

single_GET_any_test_() ->
    ServerFun = fun (Req) ->
			mochiweb_request:ok({"text/plain",
					     mochiweb_request:get(path, Req)},
					    Req)
		end,
    ClientFun = new_client_fun('GET',
			       [#treq{path = "*", xreply = <<"*">>}]),
    [{atom_to_list(Transport),
      ?_assertEqual(ok,
		    (with_server(Transport, ServerFun, ClientFun)))}
     || Transport <- [ssl, plain]].

cookie_header_test() ->
    ReplyPrefix = "You requested: ",
    ExHeaders = [{"Set-Cookie", "foo=bar"},
		 {"Set-Cookie", "foo=baz"}],
    ServerFun = fun (Req) ->
			Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
			mochiweb_request:ok({"text/plain", ExHeaders, Reply},
					    Req)
		end,
    Path = "cookie_header",
    ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
    TestReqs = [#treq{path = Path, xreply = ExpectedReply,
		      xheaders = ExHeaders}],
    ClientFun = new_client_fun('GET', TestReqs),
    ok = with_server(plain, ServerFun, ClientFun),
    ok.

do_CONNECT(Transport, Times) ->
    PathPrefix = "example.com:",
    ReplyPrefix = "You requested: ",
    ServerFun = fun (Req) ->
			Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
			mochiweb_request:ok({"text/plain", Reply}, Req)
		end,
    TestReqs = [begin
		  Path = PathPrefix ++ integer_to_list(N),
		  ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
		  #treq{path = Path, xreply = ExpectedReply}
		end
		|| N <- lists:seq(1, Times)],
    ClientFun = new_client_fun('CONNECT', TestReqs),
    ok = with_server(Transport, ServerFun, ClientFun),
    ok.

do_GET(Transport, Times) ->
    do_GET("/whatever/", Transport, Times).

do_GET(PathPrefix, Transport, Times) ->
    ReplyPrefix = "You requested: ",
    ServerFun = fun (Req) ->
			Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
			mochiweb_request:ok({"text/plain", Reply}, Req)
		end,
    TestReqs = [begin
		  Path = PathPrefix ++ integer_to_list(N),
		  ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
		  #treq{path = Path, xreply = ExpectedReply}
		end
		|| N <- lists:seq(1, Times)],
    ClientFun = new_client_fun('GET', TestReqs),
    ok = with_server(Transport, ServerFun, ClientFun),
    ok.

do_POST(Transport, Size, Times) ->
    ServerFun = fun (Req) ->
			Body = mochiweb_request:recv_body(Req),
			Headers = [{"Content-Type",
				    "application/octet-stream"}],
			mochiweb_request:respond({201, Headers, Body}, Req)
		end,
    TestReqs = [begin
		  Path = "/stuff/" ++ integer_to_list(N),
		  Body = crypto:strong_rand_bytes(Size),
		  #treq{path = Path, body = Body, xreply = Body}
		end
		|| N <- lists:seq(1, Times)],
    ClientFun = new_client_fun('POST', TestReqs),
    ok = with_server(Transport, ServerFun, ClientFun),
    ok.

new_client_fun(Method, TestReqs) ->
    fun (Transport, Port) ->
	    mochiweb_test_util:client_request(Transport, Port,
					      Method, TestReqs)
    end.

close_on_unread_data_test() ->
    ok = with_server(plain,
		     fun mochiweb_request:not_found/1,
		     fun close_on_unread_data_client/2).

close_on_unread_data_client(Transport, Port) ->
    SockFun = mochiweb_test_util:sock_fun(Transport, Port),
    %% A normal GET request should not trigger this behavior
    Request0 = string:join(["GET / HTTP/1.1",
			    "Host: localhost", "", ""],
			   "\r\n"),
    ok = SockFun({setopts, [{packet, http}]}),
    ok = SockFun({send, Request0}),
    ?assertMatch({ok, {http_response, {1, 1}, 404, _}},
		 (SockFun(recv))),
    Headers0 =
	mochiweb_test_util:read_server_headers(SockFun),
    ?assertEqual(undefined,
		 (mochiweb_headers:get_value("Connection", Headers0))),
    Len0 =
	list_to_integer(mochiweb_headers:get_value("Content-Length",
						   Headers0)),
    _Body0 = mochiweb_test_util:drain_reply(SockFun, Len0,
					    <<>>),
    %% Re-use same socket
    Request = string:join(["POST / HTTP/1.1",
			   "Host: localhost", "Content-Type: application/json",
			   "Content-Length: 2", "", "{}"],
			  "\r\n"),
    ok = SockFun({setopts, [{packet, http}]}),
    ok = SockFun({send, Request}),
    ?assertMatch({ok, {http_response, {1, 1}, 404, _}},
		 (SockFun(recv))),
    Headers =
	mochiweb_test_util:read_server_headers(SockFun),
    %% Expect to see a Connection: close header when we know the
    %% server will close the connection re #146
    ?assertEqual("close",
		 (mochiweb_headers:get_value("Connection", Headers))),
    Len =
	list_to_integer(mochiweb_headers:get_value("Content-Length",
						   Headers)),
    _Body = mochiweb_test_util:drain_reply(SockFun, Len,
					   <<>>),
    ?assertEqual({error, closed}, (SockFun(recv))),
    ok.
