nealrichardson / httptest2

Utilities for testing R 📦s that use httr2
https://enpiar.com/httptest2/
Other
26 stars 6 forks source link

Mock requests which return empty body #20

Closed mpadge closed 12 months ago

mpadge commented 2 years ago

That's currently not possible, but happens comonly in DELETE methods which return only a status. The body of all responses is passed straight through to http2::resp_body_...() which errors with Can not retrieve empty body. Would it be possible to first check for empty response body elements, and just mock requests but not responses if empty?

nealrichardson commented 2 years ago

Can you give an example of something that fails unexpectedly?

mpadge commented 2 years ago

It's a bit convoluted, but this is the code I was using at the time. It requires an API token from figshare, with which it first creates a dummy article then deletes it.

library (httptest2)
library (testthat)

url <- "https://api.figshare.com/v2/account/articles"
token <- Sys.getenv ("FIGSHARE_TOKEN")
body <- list (title = "New Title") # need something in body to post call
req_create <- httr2::request (url) |>
    httr2::req_headers ("Authorization" = paste0 ("Bearer ", token)) |>
    httr2::req_method ("POST") |>
    httr2::req_body_json (data = body)

test_that ("test", {

    resp <- with_mock_dir ("mock_create", {
        httr2::req_perform (req_create)
    })
    data <- httr2::resp_body_json (resp)
    id <- data$entity_id

    # then delete that record:
    url_id <- paste0 (url, "/", id)
    req_delete <- httr2::request (url_id) |>
        httr2::req_headers ("Authorization" = paste0 ("Bearer ", token)) |>
        httr2::req_method ("DELETE")
    resp <- with_mock_dir ("mock_delete", {
        httr2::req_perform (req_delete)
    })
    print (resp) # body is empty; 204 is expected code for delete methods
})

That code works on its own without any problems, but when run within a package which uses package-level redactors gives this error (with all directories first cleaned, so this happens on initial recording of results):

library (<my_pkg>)
library (httptest2)
testthat::test_file ("tests/testthat/test-file.R")
#> 
#> ══ Testing test-file.R ══════════════════════════════════════════════
#> 
#> [ FAIL 1 | WARN 0 | SKIP 0 | PASS 24 ]
#> 
#> ── Error (test-file.R:135:5): test context ──────────────────────
#> Error in `resp_body_raw(resp)`: Can not retrieve empty body
#> Backtrace:
#>   1. httptest2::with_mock_dir(...)
#>        at test-client-figshare.R:135:4
#>   8. cli$deposit_delete(deposit_id)
#>        at test-client-figshare.R:136:8
#>   9. httr2::req_perform(req)
#>        at deposits/R/client-main.R:243:12
#>  19. global redactor(resp)
#>  20. httptest2::gsub_response(...)
#>        at inst/httptest2/redact.R:3:4
#>  21. httptest2::within_body_text(response, replacer)
#>  22. httr2::resp_body_string(response)
#>  23. httr2::resp_body_raw(resp)
#> 
#> 
#> [ FAIL 1 | WARN 0 | SKIP 0 | PASS 24 ]

Created on 2022-06-01 by the reprex package (v2.0.1)

And it's clearly happening because httptest2 is applying the redactors to all responses, including those that are empty. Hopefully that's enough for you to diagnose and fix without having to run this actual code. start_capturing() passes through to save_response(), but transforms the response directly there via redactor(resp), so the redactor is applied even if the response is empty, which causes the error.

nealrichardson commented 12 months ago

Thanks for the report and apologies for the delayed response!