/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>

#include <apr.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_version.h>

#include "serf.h"

#include "test_serf.h"

/* Validate that requests are sent and completed in the order of creation. */
static void test_serf_connection_request_create(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[2];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    apr_status_t status;
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"),
                 HeaderEqualTo("Host", tb->serv_host))
        Respond(WithCode(200), WithChunkedBody(""))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"),
                 HeaderEqualTo("Host", tb->serv_host))
        Respond(WithCode(200), WithChunkedBody(""))
    EndGiven

    create_new_request(tb, &handler_ctx[0], "GET", "/", 1);
    create_new_request(tb, &handler_ctx[1], "GET", "/", 2);

    status = run_client_and_mock_servers_loops(tb, num_requests,
                                               handler_ctx, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Check that the requests were sent and reveived by the server in the order
       we created them */
    Verify(tb->mh)
      CuAssertTrue(tc, VerifyAllRequestsReceivedInOrder);
    EndVerify

    /* Check that the responses were received in the order we created them */
    for (i = 0; i < tb->handled_requests->nelts; i++) {
        int req_nr = APR_ARRAY_IDX(tb->handled_requests, i, int);
        CuAssertIntEquals(tc, i + 1, req_nr);
    }
}

/* Validate that priority requests are sent and completed before normal
   requests. */
static void test_serf_connection_priority_request_create(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[3];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    apr_status_t status;
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"),
                 HeaderEqualTo("Host", tb->serv_host))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"),
                 HeaderEqualTo("Host", tb->serv_host))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"),
                 HeaderEqualTo("Host", tb->serv_host))
    EndGiven

    create_new_request(tb, &handler_ctx[0], "GET", "/", 2);
    create_new_request(tb, &handler_ctx[1], "GET", "/", 3);
    create_new_prio_request(tb, &handler_ctx[2], "GET", "/", 1);

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);

    /* Check that the responses were received in the order we created them */
    for (i = 0; i < tb->handled_requests->nelts; i++) {
        int req_nr = APR_ARRAY_IDX(tb->handled_requests, i, int);
        CuAssertIntEquals(tc, i + 1, req_nr);
    }
}

/* Test that serf correctly handles the 'Connection:close' header when the
   server is planning to close the connection. */
static void test_closed_connection(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[10];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* We will send 10 requests to the mock server, close connection after the
       4th and the 8th response */
    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("4"))
        Respond(WithCode(200), WithRequestBody,
                WithConnectionCloseHeader)
      /* All messages from hereon can potentially be sent (but not responded to)
         twice */
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("5"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("6"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("7"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("8"))
        Respond(WithCode(200), WithRequestBody,
                WithConnectionCloseHeader)
      /* All messages from hereon can potentially be sent (but not responded to)
         three times */
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("9"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("10"))
    EndGiven

    /* Send some requests on the connections */
    for (i = 0 ; i < num_requests ; i++) {
        create_new_request(tb, &handler_ctx[i], "GET", "/", i+1);
    }

    status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx,
                                               tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Check that the requests were sent and reveived by the server */
    Verify(tb->mh)
      CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived);
    EndVerify
    CuAssertTrue(tc, tb->sent_requests->nelts >= num_requests);
    CuAssertIntEquals(tc, num_requests, tb->accepted_requests->nelts);
    CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts);
}

/* Default implementation of a serf_connection_setup_t callback. */
static apr_status_t http_conn_setup_mock_socket(apr_socket_t *skt,
                                                serf_bucket_t **input_bkt,
                                                serf_bucket_t **output_bkt,
                                                void *setup_baton,
                                                apr_pool_t *pool)
{
    test_baton_t *tb = setup_baton;

    serf_bucket_t *skt_bkt = serf_context_bucket_socket_create(tb->context,
                                                               skt,
                                                               tb->bkt_alloc);
    *input_bkt = serf_bucket_mock_sock_create(skt_bkt,
                                              tb->user_baton_l,
                                              tb->bkt_alloc);

    return APR_SUCCESS;
}

static void
send_more_requests_than_keepalive_of_server(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[10];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    int i;
    apr_status_t status;

    /* We will send 10 requests to the mock server, close connection after the
       4th and the 8th response */
    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("4"))
        Respond(WithCode(200), WithRequestBody)
        CloseConnection
      /* All messages from hereon can potentially be sent (but not responded to)
         twice */
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("5"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("6"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("7"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("8"))
        Respond(WithCode(200), WithRequestBody)
        CloseConnection
      /* All messages from hereon can potentially be sent (but not responded to)
         three times */
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("9"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("10"))
    EndGiven

    /* Send some requests on the connections */
    for (i = 0 ; i < num_requests ; i++) {
        create_new_request(tb, &handler_ctx[i], "GET", "/", i+1);
    }

    status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx,
                                               tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Check that the requests were sent and reveived by the server */
    Verify(tb->mh)
      CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived);
    EndVerify
    CuAssertTrue(tc, tb->sent_requests->nelts >= num_requests);
    CuAssertTrue(tc, tb->accepted_requests->nelts >= num_requests);
    CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts);

}

/* Test that serf correctly handles suddenly closed connections (where the last
   response didn't have a Connection: close header). It should be handled as a
   normal connection closure. */
static void test_eof_connection(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    send_more_requests_than_keepalive_of_server(tc);
}

/* The same test as test_eof_connection, but with the authn callback set.
   This makes serf follow a slightly different code path in handle_response(). */
static void test_eof_connection_with_authn_cb(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    serf_config_credentials_callback(tb->context, dummy_authn_callback);
    send_more_requests_than_keepalive_of_server(tc);
}

/* Test that serf correctly handles aborted connections. This can happen
   on Windows when the server (cleanly) closes the connection, and where it 
   happens between responses, it should be handled as a normal connection
   closure. */
static void test_aborted_connection(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. Use the mock socket to return
       APR_ECONNABORTED instead of APR_EOF. */
    setup_test_mock_server(tb);
    tb->user_baton_l = APR_ECONNABORTED;
    status = setup_test_client_context(tb, http_conn_setup_mock_socket,
                                       tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    send_more_requests_than_keepalive_of_server(tc);
}

/* The same test as test_aborted_connection, but with the authn callback set.
   This makes serf follow a slightly different code path in handle_response(). */
static void test_aborted_connection_with_authn_cb(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. Use the mock socket to return
     APR_ECONNABORTED instead of APR_EOF. */
    setup_test_mock_server(tb);
    tb->user_baton_l = APR_ECONNABORTED;
    status = setup_test_client_context(tb, http_conn_setup_mock_socket,
                                       tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    serf_config_credentials_callback(tb->context, dummy_authn_callback);
    send_more_requests_than_keepalive_of_server(tc);
}

/* The same test as test_aborted_connection, but with APR_ECONNRESET instead
   of APR_ECONNABORTED. */
static void test_reset_connection(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. Use the mock socket to return
       APR_ECONNRESET instead of APR_EOF. */
    setup_test_mock_server(tb);
    tb->user_baton_l = APR_ECONNRESET;
    status = setup_test_client_context(tb, http_conn_setup_mock_socket,
                                       tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    send_more_requests_than_keepalive_of_server(tc);
}

/* The same test as test_reset_connection, but with the authn callback set.
   This makes serf follow a slightly different code path in handle_response(). */
static void test_reset_connection_with_authn_cb(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;

    /* Set up a test context with a server. Use the mock socket to return
       APR_ECONNRESET instead of APR_EOF. */
    setup_test_mock_server(tb);
    tb->user_baton_l = APR_ECONNRESET;
    status = setup_test_client_context(tb, http_conn_setup_mock_socket,
                                       tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    serf_config_credentials_callback(tb->context, dummy_authn_callback);
    send_more_requests_than_keepalive_of_server(tc);
}

/* Test if serf is sending the request to the proxy, not to the server
   directly. */
static void test_setup_proxy(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[1];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    apr_status_t status;

    /* Set up a test context with a proxy */
    setup_test_mock_server(tb);
    status = setup_test_mock_proxy(tb);
    CuAssertIntEquals(tc, APR_SUCCESS, status);
    status = setup_test_client_context_with_proxy(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      RequestsReceivedByProxy
        GETRequest(
            URLEqualTo(apr_psprintf(tb->pool, "http://%s", tb->serv_host)),
            HeaderEqualTo("Host", tb->serv_host),
            ChunkedBodyEqualTo("1"))
          Respond(WithCode(200), WithChunkedBody(""))
    EndGiven
    create_new_request(tb, &handler_ctx[0], "GET", "/", 1);

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);
}

/*****************************************************************************
 * Test if we can make serf send requests one by one.
 *****************************************************************************/

/* Resend the first request 2 more times as priority requests. */
static apr_status_t
handle_response_keepalive_limit(serf_request_t *request,
                                serf_bucket_t *response,
                                void *handler_baton,
                                apr_pool_t *pool)
{
    handler_baton_t *ctx = handler_baton;

    if (! response) {
        return APR_SUCCESS;
    }

    while (1) {
        apr_status_t status;
        const char *data;
        apr_size_t len;

        status = serf_bucket_read(response, 2048, &data, &len);
        if (SERF_BUCKET_READ_ERROR(status)) {
            return status;
        }

        if (APR_STATUS_IS_EOF(status)) {
            APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
            ctx->done = TRUE;
            if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) {
                serf_connection_priority_request_create(ctx->tb->connection,
                                                        setup_request,
                                                        ctx);
                ctx->done = FALSE;
            }
            return APR_EOF;
        }
    }

    return APR_SUCCESS;
}

#define SENT_REQUESTS 5
#define RCVD_REQUESTS 7
static void test_keepalive_limit_one_by_one(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[RCVD_REQUESTS];
    int i;


    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Reduce the bandwidth to one at a time. The first request will be resend
       twice as priority requests, so iff the bandwidth reduction is in effect 
       these should be sent before all other requests. */
    serf_connection_set_max_outstanding_requests(tb->connection, 1);

    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("4"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("5"))
    EndGiven

    for (i = 0 ; i < SENT_REQUESTS ; i++) {
        create_new_request_with_resp_hdlr(tb, &handler_ctx[i], "GET", "/", i+1,
                                          handle_response_keepalive_limit);
    }

    /* The two retries of request 1 both also have req_id=1, which means that
       we can't expected RECV_REQUESTS # of requests here, because the done flag
       of these 2 request will not be registered correctly. */
    status = run_client_and_mock_servers_loops(tb, SENT_REQUESTS, handler_ctx,
                                               tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Verify(tb->mh)
      CuAssert(tc, ErrorMessage, VerifyAllRequestsReceivedInOrder);
    EndVerify

    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->sent_requests->nelts);
    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->accepted_requests->nelts);
    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->handled_requests->nelts);
}
#undef SEND_REQUESTS
#undef RCVD_REQUESTS

/*****************************************************************************
 * Test if we can make serf first send requests one by one, and then change
 * back to burst mode.
 *****************************************************************************/
#define SENT_REQUESTS 5
#define RCVD_REQUESTS 7
/* Resend the first request 2 times by reducing the pipeline bandwidth to
   one request at a time, and by adding the first request again at the start of
   the outgoing queue. */
static apr_status_t
handle_response_keepalive_limit_burst(serf_request_t *request,
                                      serf_bucket_t *response,
                                      void *handler_baton,
                                      apr_pool_t *pool)
{
    handler_baton_t *ctx = handler_baton;

    if (! response) {
        return APR_SUCCESS;
    }

    while (1) {
        apr_status_t status;
        const char *data;
        apr_size_t len;

        status = serf_bucket_read(response, 2048, &data, &len);
        if (SERF_BUCKET_READ_ERROR(status)) {
            return status;
        }

        if (APR_STATUS_IS_EOF(status)) {
            APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
            ctx->done = TRUE;
            if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) {
                serf_connection_priority_request_create(ctx->tb->connection,
                                                        setup_request,
                                                        ctx);
                ctx->done = FALSE;
            }
            else  {
                /* No more one-by-one. */
                serf_connection_set_max_outstanding_requests(ctx->tb->connection,
                                                             0);
            }
            return APR_EOF;
        }

        if (APR_STATUS_IS_EAGAIN(status)) {
            return status;
        }
    }

    return APR_SUCCESS;
}

static void test_keepalive_limit_one_by_one_and_burst(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
        apr_status_t status;
    handler_baton_t handler_ctx[SENT_REQUESTS];
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    serf_connection_set_max_outstanding_requests(tb->connection, 1);

    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("4"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("5"))
    EndGiven

    for (i = 0 ; i < SENT_REQUESTS ; i++) {
        create_new_request_with_resp_hdlr(tb, &handler_ctx[i], "GET", "/", i+1,
                                          handle_response_keepalive_limit_burst);
    }

    /* The two retries of request 1 both also have req_id=1, which means that
       we can't expected RECV_REQUESTS # of requests here, because the done flag
       of these 2 request will not be registered correctly. */
    status = run_client_and_mock_servers_loops(tb, SENT_REQUESTS, handler_ctx,
                                               tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Verify(tb->mh)
      CuAssert(tc, ErrorMessage, VerifyAllRequestsReceivedInOrder);
    EndVerify

    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->sent_requests->nelts);
    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->accepted_requests->nelts);
    CuAssertIntEquals(tc, RCVD_REQUESTS, tb->handled_requests->nelts);
}
#undef SEND_REQUESTS
#undef RCVD_REQUESTS

typedef struct progress_baton_t {
  apr_off_t read;
  apr_off_t written;
} progress_baton_t;

static void
progress_cb(void *progress_baton, apr_off_t read, apr_off_t written)
{
    test_baton_t *tb = progress_baton;
    progress_baton_t *pb = tb->user_baton;

    pb->read = read;
    pb->written = written;
}

static apr_status_t progress_conn_setup(apr_socket_t *skt,
                                          serf_bucket_t **input_bkt,
                                          serf_bucket_t **output_bkt,
                                          void *setup_baton,
                                          apr_pool_t *pool)
{
    test_baton_t *tb = setup_baton;
    *input_bkt = serf_context_bucket_socket_create(tb->context, skt,
                                                   tb->bkt_alloc);
    return APR_SUCCESS;
}

static void test_progress_callback(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[5];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    int i;
    progress_baton_t *pb;


    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, progress_conn_setup, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Set up the progress callback. */
    pb = apr_pcalloc(tb->pool, sizeof(*pb));
    tb->user_baton = pb;
    serf_context_set_progress_cb(tb->context, progress_cb, tb);

    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("3"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("4"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("5"))
    EndGiven

    /* Send some requests on the connections */
    for (i = 0 ; i < num_requests ; i++) {
        create_new_request(tb, &handler_ctx[i], "GET", "/", i+1);
    }

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);

    /* Check that progress was reported. */
    CuAssertTrue(tc, pb->written > 0);
    CuAssertTrue(tc, pb->read > 0);
}

/* Test that username:password components in url are ignored. */
static void test_connection_userinfo_in_url(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[2];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("2"))
    EndGiven

    /* Create a connection using user:password@hostname syntax */
    tb->serv_url = apr_psprintf(tb->pool, "http://user:password@localhost:%d",
                                tb->serv_port);

    use_new_connection(tb, tb->pool);

    /* Send some requests on the connections */
    for (i = 0 ; i < num_requests ; i++) {
        create_new_request(tb, &handler_ctx[i], "GET", "/", i+1);
    }

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);
}

/*****************************************************************************
 * Issue #91: test that serf correctly handle an incoming 4xx reponse while
 * the outgoing request wasn't written completely yet.
 *****************************************************************************/

#define REQUEST_BODY_PART1\
    "<?xml version=""1.0"" encoding=""utf-8""?><propfind xmlns=""DAV:""><prop>"

#define REQUEST_PART1 "PROPFIND / HTTP/1.1" CRLF\
"Host: lgo-ubuntu.local" CRLF\
"User-Agent: SVN/1.8.0-dev (x86_64-apple-darwin11.4.2) serf/2.0.0" CRLF\
"Content-Type: text/xml" CRLF\
"Transfer-Encoding: chunked" CRLF \
CRLF\
"12d" CRLF\
"<?xml version=""1.0"" encoding=""utf-8""?><propfind xmlns=""DAV:""><prop>"


#define REQUEST_PART2 \
"<resourcetype xmlns=""DAV:""/><getcontentlength xmlns=""DAV:""/>"\
"<deadprop-count xmlns=""http://subversion.tigris.org/xmlns/dav/""/>"\
"<version-name xmlns=""DAV:""/><creationdate xmlns=""DAV:""/>"\
"<creator-displayname xmlns=""DAV:""/></prop></propfind>" CRLF\
"0" CRLF \
CRLF

#define RESPONSE_408 "HTTP/1.1 408 Request Time-out" CRLF\
"Date: Wed, 14 Nov 2012 19:50:35 GMT" CRLF\
"Server: Apache/2.2.17 (Ubuntu)" CRLF\
"Vary: Accept-Encoding" CRLF\
"Content-Length: 305" CRLF\
"Connection: close" CRLF\
"Content-Type: text/html; charset=iso-8859-1" CRLF \
CRLF\
"<!DOCTYPE HTML PUBLIC ""-//IETF//DTD HTML 2.0//EN""><html><head>"\
"<title>408 Request Time-out</title></head><body><h1>Request Time-out</h1>"\
"<p>Server timeout waiting for the HTTP request from the client.</p><hr>"\
"<address>Apache/2.2.17 (Ubuntu) Server at lgo-ubuntu.local Port 80</address>"\
"</body></html>"


static apr_status_t queue_part2(void *baton, serf_bucket_t *aggregate_bucket)
{
    serf_bucket_t *body_bkt;
    handler_baton_t *ctx = baton;

    if (ctx->done) {
        body_bkt = serf_bucket_simple_create(REQUEST_PART1, strlen(REQUEST_PART2),
                                             NULL, NULL,
                                             ctx->tb->bkt_alloc);
        serf_bucket_aggregate_append(aggregate_bucket, body_bkt);
    }

    return APR_EAGAIN;
}

static apr_status_t setup_request_timeout(
                                  serf_request_t *request,
                                  void *setup_baton,
                                  serf_bucket_t **req_bkt,
                                  serf_response_acceptor_t *acceptor,
                                  void **acceptor_baton,
                                  serf_response_handler_t *handler,
                                  void **handler_baton,
                                  apr_pool_t *pool)
{
    handler_baton_t *ctx = setup_baton;
    serf_bucket_t *body_bkt;

    *req_bkt = serf_bucket_aggregate_create(serf_request_get_alloc(request));

    /* create a simple body text */
    body_bkt = serf_bucket_simple_create(REQUEST_PART1, strlen(REQUEST_PART1),
                                         NULL, NULL,
                                         serf_request_get_alloc(request));
    serf_bucket_aggregate_append(*req_bkt, body_bkt);

    /* When REQUEST_PART1 runs out, we will queue up PART2.  */
    serf_bucket_aggregate_hold_open(*req_bkt, queue_part2, ctx);

    APR_ARRAY_PUSH(ctx->sent_requests, int) = ctx->req_id;

    *acceptor = ctx->acceptor;
    *acceptor_baton = ctx;
    *handler = ctx->handler;
    *handler_baton = ctx;

    return APR_SUCCESS;
}

static apr_status_t handle_response_timeout(
                                    serf_request_t *request,
                                    serf_bucket_t *response,
                                    void *handler_baton,
                                    apr_pool_t *pool)
{
    handler_baton_t *ctx = handler_baton;
    serf_status_line sl;
    apr_status_t status;

    if (! response) {
        serf_connection_request_create(ctx->tb->connection,
                                       setup_request,
                                       ctx);
        return APR_SUCCESS;
    }

    if (serf_request_is_written(request) != APR_EBUSY) {
        return SERF_ERROR_ISSUE_IN_TESTSUITE;
    }


    status = serf_bucket_response_status(response, &sl);
    if (SERF_BUCKET_READ_ERROR(status)) {
        return status;
    }
    if (!sl.version && (APR_STATUS_IS_EOF(status) ||
                        APR_STATUS_IS_EAGAIN(status))) {
        return status;
    }
    if (sl.code == 408) {
        APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
        ctx->done = TRUE;
    }

    /* discard the rest of the body */
    while (1) {
        const char *data;
        apr_size_t len;

        status = serf_bucket_read(response, 2048, &data, &len);
        if (SERF_BUCKET_READ_ERROR(status) ||
            APR_STATUS_IS_EAGAIN(status) ||
            APR_STATUS_IS_EOF(status))
            return status;
    }

    return APR_SUCCESS;
}

static void test_request_timeout(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[1];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      HTTPRequest(MethodEqualTo("PROPFIND"),
                  URLEqualTo("/"),
                  IncompleteBodyEqualTo(REQUEST_BODY_PART1))
        Respond(WithRawData(RESPONSE_408, strlen(RESPONSE_408)))
    EndGiven

    /* Send a incomplete requesta on the connection */
    handler_ctx[0].method = "PROPFIND";
    handler_ctx[0].path = "/";
    handler_ctx[0].done = FALSE;

    handler_ctx[0].acceptor = accept_response;
    handler_ctx[0].acceptor_baton = NULL;
    handler_ctx[0].handler = handle_response_timeout;
    handler_ctx[0].req_id = 1;
    handler_ctx[0].accepted_requests = tb->accepted_requests;
    handler_ctx[0].sent_requests = tb->sent_requests;
    handler_ctx[0].handled_requests = tb->handled_requests;
    handler_ctx[0].tb = tb;

    serf_connection_request_create(tb->connection,
                                   setup_request_timeout,
                                   &handler_ctx[0]);

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);
}

/* Validate reading a large chunked response. */
static void test_connection_large_response(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[1];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    apr_status_t status;


    /* create large chunked response message */
    const char *response = create_large_response_message(tb->pool);

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    Given(tb->mh)
      GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"))
        Respond(WithRawData(response, strlen(response)))
    EndGiven

    create_new_request(tb, &handler_ctx[0], "GET", "/", 1);

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);
}

/* Validate sending a large chunked response. */
static void test_connection_large_request(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    handler_baton_t handler_ctx[1];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    const char *request, *body;
    apr_status_t status;


    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* create large chunked request message */
    body = create_large_request_message_body(tb->pool);
    request = create_large_request_message(tb->pool, body);

    Given(tb->mh)
      GETRequest(URLEqualTo("/"), RawBodyEqualTo(body))
        Respond(WithCode(200), WithChunkedBody(""))
    EndGiven

    create_new_request(tb, &handler_ctx[0], "GET", "/", 1);
    handler_ctx[0].request = request;

    run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests,
                                                handler_ctx, tb->pool);
}

static void test_max_keepalive_requests(CuTest *tc)
{
    test_baton_t *tb = tc->testBaton;
    apr_status_t status;
    handler_baton_t handler_ctx[200];
    const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
    int i;

    /* Set up a test context with a server */
    setup_test_mock_server(tb);
    status = setup_test_client_context(tb, NULL, tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    InitMockServers(tb->mh)
      ConfigServerWithID("server", WithMaxKeepAliveRequests(4))
    EndInit

    /* We will NUM_REQUESTS requests to the mock server, close connection after 
       every 4th response. */
    Given(tb->mh)
      DefaultResponse(WithCode(200), WithRequestBody)

      GETRequest(URLEqualTo("/index.html"))
    EndGiven

    /* Send some requests on the connections */
    for (i = 0 ; i < num_requests ; i++) {
        create_new_request(tb, &handler_ctx[i], "GET", "/index.html", i+1);
    }

    status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx,
                                               tb->pool);
    CuAssertIntEquals(tc, APR_SUCCESS, status);

    /* Check that the requests were sent and reveived by the server */
    Verify(tb->mh)
      CuAssert(tc, ErrorMessage, VerifyAllRequestsReceived);
      CuAssertIntEquals(tc, num_requests, VerifyStats->requestsResponded);
    EndVerify
    CuAssertTrue(tc, tb->sent_requests->nelts >= num_requests);
    CuAssertIntEquals(tc, num_requests, tb->accepted_requests->nelts);
    CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts);
}

/*****************************************************************************/
CuSuite *test_context(void)
{
    CuSuite *suite = CuSuiteNew();

    CuSuiteSetSetupTeardownCallbacks(suite, test_setup, test_teardown);

    SUITE_ADD_TEST(suite, test_serf_connection_request_create);
    SUITE_ADD_TEST(suite, test_serf_connection_priority_request_create);
    SUITE_ADD_TEST(suite, test_closed_connection);
    SUITE_ADD_TEST(suite, test_eof_connection);
    SUITE_ADD_TEST(suite, test_eof_connection_with_authn_cb);
    SUITE_ADD_TEST(suite, test_aborted_connection);
    SUITE_ADD_TEST(suite, test_aborted_connection_with_authn_cb);
    SUITE_ADD_TEST(suite, test_reset_connection);
    SUITE_ADD_TEST(suite, test_reset_connection_with_authn_cb);
    SUITE_ADD_TEST(suite, test_setup_proxy);
    SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one);
    SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one_and_burst);
    SUITE_ADD_TEST(suite, test_progress_callback);
    SUITE_ADD_TEST(suite, test_connection_userinfo_in_url);
    SUITE_ADD_TEST(suite, test_request_timeout);
    SUITE_ADD_TEST(suite, test_connection_large_response);
    SUITE_ADD_TEST(suite, test_connection_large_request);
    SUITE_ADD_TEST(suite, test_max_keepalive_requests);

    return suite;
}
