Groovy HttpBuilder - получить сообщение о неисправности

Я пытаюсь использовать Groovy HTTPBuilder для записи теста интеграции, который будет проверять правильное сообщение об ошибке в теле вместе с сообщением о статусе HTTP 409. Тем не менее, я не могу понять, как фактически получить доступ к телу ответа HTTP в случаях сбоя.

http.request(ENV_URL, Method.POST, ContentType.TEXT) {
    uri.path = "/curate/${id}/submit"
    contentType = ContentType.JSON
    response.failure = { failresp_inner ->
        failresp = failresp_inner
    }
}

then:
assert failresp.status == 409
// I would like something like 
//assert failresp.data == "expected error message"

Вот как выглядит HTTP-ответ с сервера:

2013-11-13 18:17:58,726 DEBUG  wire - << "HTTP/1.1 409 Conflict[\r][\n]"
2013-11-13 18:17:58,726 DEBUG  wire - << "Date: Wed, 13 Nov 2013 23:17:58 GMT[\r][\n]"
2013-11-13 18:17:58,726 DEBUG  wire - << "Content-Type: text/plain[\r][\n]"
2013-11-13 18:17:58,726 DEBUG  wire - << "Transfer-Encoding: chunked[\r][\n]"
2013-11-13 18:17:58,727 DEBUG  wire - << "[\r][\n]"
2013-11-13 18:17:58,728 DEBUG  wire - << "E[\r][\n]"
2013-11-13 18:17:58,728 DEBUG  wire - << "expected error message"
2013-11-13 18:17:58,728 DEBUG  wire - << "[\r][\n]"
2013-11-13 18:17:58,728 DEBUG  wire - << "0[\r][\n]"
2013-11-13 18:17:58,728 DEBUG  wire - << "[\r][\n]"

Ответ 1

Недавно я просто боролся с этим, пытаясь интегрировать тесты на конечные точки REST с помощью Spock. Я использовал Сэма в качестве вдохновения и в конечном итоге улучшил его, чтобы продолжать использовать автокастинг, который предоставляет HttpBuilder. После некоторого времени возиться, у меня была яркая идея просто назначить закрытие обработчика успеха обработчику отказа, чтобы стандартизировать поведение независимо от того, какой код состояния возвращается.

client.handler.failure = client.handler.success

Пример этого в действии:

...

import static org.apache.http.HttpStatus.*

...

private RESTClient createClient(String username = null, String password = null) {
    def client = new RESTClient(BASE_URL)
    client.handler.failure = client.handler.success

    if(username != null)
        client.auth.basic(username, password)

    return client
}

...

def unauthenticatedClient = createClient()
def userClient = createClient(USER_USERNAME, USER_PASSWORD)
def adminClient = createClient(ADMIN_USERNAME, ADMIN_PASSWORD)

...

def 'get account'() {
    expect:
    // unauthenticated tries to get user account
    unauthenticatedClient.get([path: "account/$USER_EMAIL"]).status == SC_UNAUTHENTICATED

    // user gets user account
    with(userClient.get([path: "account/$USER_EMAIL"])) {
        status == SC_OK
        with(responseData) {
            email == USER_EMAIL
            ...
        }
    }

    // user tries to get user2 account
    with(userClient.get([path: "account/$USER2_EMAIL"])) {
        status == SC_FORBIDDEN
        with(responseData) {
            message.contains(USER_EMAIL)
            message.contains(USER2_EMAIL)
            ...
        }
    }

    // admin to get user account
    with(adminClient.get([path: "account/$USER_EMAIL"])) {
        status == SC_OK
        with(responseData) {
            email == USER_EMAIL
            ...
        }
    }
}

Ответ 2

Работает ли он, если вы используете:

response.failure = { resp, reader ->
    failstatus = resp.statusLine
    failresp   = reader.text
}

Ответ 3

Я также боролся с этим, когда начал использовать HttpBuilder. Решение, с которым я столкнулся, состояло в том, чтобы определить успешность HTTPBuilder и сбои при сбое, чтобы вернуть согласованные значения следующим образом:

HTTPBuilder http = new HTTPBuilder()
http.handler.failure = { resp, reader ->
    [response:resp, reader:reader]
}
http.handler.success = { resp, reader ->
    [response:resp, reader:reader]
}

Таким образом, ваш экземпляр HTTPBuilder будет последовательно возвращать карту, содержащую объект ответа (экземпляр HttpResponseDecorator) и объект-читатель. Тогда ваш запрос будет выглядеть следующим образом:

def map = http.request(ENV_URL, Method.POST, ContentType.TEXT) {
    uri.path = "/curate/${id}/submit"
    contentType = ContentType.JSON
}

def response = map['response']
def reader = map['reader']

assert response.status == 409

Читатель будет своего рода объектом, который даст вам доступ к телу ответа, тип которого вы можете определить, вызвав метод getClass():

println "reader type: ${reader.getClass()}"

Тип объекта считывателя будет определяться заголовком Content-Type в ответе. Вы можете указать серверу конкретно, что вы хотите вернуть, добавив в запрос заголовок "Принять".