test_that("checks its inputs", { req <- request_test("/status/:status", status = 200) expect_snapshot(error = TRUE, { req_perform_promise(1) req_perform_promise(req, path = 1) req_perform_promise(req, pool = "INVALID") req_perform_promise(req, verbosity = "INVALID") }) }) test_that("returns a promise that resolves", { p1 <- req_perform_promise(request_test("/delay/:secs", secs = 0.25)) p2 <- req_perform_promise(request_test("/delay/:secs", secs = 0.25)) expect_s3_class(p1, "promise") expect_s3_class(p2, "promise") p1_value <- extract_promise(p1) expect_equal(resp_status(p1_value), 200) p2_value <- extract_promise(p2) expect_equal(resp_status(p2_value), 200) }) test_that("correctly prepares request", { req <- request_test("/post") |> req_method("POST") prom <- req_perform_promise(req) expect_no_error(extract_promise(prom)) }) test_that("correctly prepares request", { req <- request_test("/get") expect_snapshot( . <- extract_promise(req_perform_promise(req, verbosity = 1)), transform = function(x) { gsub( "(Date|Host|User-Agent|ETag|Content-Length|Accept-Encoding): .*", "\\1: ", x ) } ) }) test_that("can promise to download files", { req <- request_test("/json") path <- withr::local_tempfile() p <- req_perform_promise(req, path) expect_s3_class(p, "promise") p_value <- extract_promise(p) expect_equal(p_value$body, new_path(path)) # And check that something was downloaded expect_gt(file.size(path), 0) }) test_that("promises can retrieve from cache", { req <- request("http://example.com") |> req_cache(tempfile()) resp <- response( 200, headers = "Expires: Wed, 01 Jan 3000 00:00:00 GMT", body = charToRaw("abc") ) cache_set(req, resp) p <- req_perform_promise(req) expect_s3_class(p, "promise") p_value <- extract_promise(p) expect_equal(p_value, resp) }) test_that("both curl and HTTP errors in promises are rejected", { expect_error( extract_promise( req_perform_promise(request_test("/status/:status", status = 404)) ), class = "httr2_http_404" ) expect_error( extract_promise( req_perform_promise(request("INVALID")) ), class = "httr2_failure" ) }) test_that("req_perform_promise doesn't leave behind poller", { skip_if_not( later::loop_empty(), "later::global_loop not empty when test started" ) p <- req_perform_promise(request_test("/delay/:secs", secs = 0.25)) # Before promise is resolved, there should be an operation in our later loop expect_false(later::loop_empty()) p_value <- extract_promise(p) # But now that that our promise is resolved, we shouldn't still be polling the pool expect_true(later::loop_empty()) }) test_that("req_perform_promise can use non-default pool", { custom_pool <- curl::new_pool() p1 <- req_perform_promise(request_test("/delay/:secs", secs = 0.25)) p2 <- req_perform_promise( request_test("/delay/:secs", secs = 0.25), pool = custom_pool ) expect_equal(length(curl::multi_list(custom_pool)), 1) p1_value <- extract_promise(p1) expect_equal(resp_status(p1_value), 200) p2_value <- extract_promise(p2) expect_equal(resp_status(p2_value), 200) }) test_that("req_perform_promise uses the default loop", { # The main reason for temp loops is to allow an asynchronous operation to be # created, waited on, and resolved/rejected inside of a synchronous function, # all without affecting any asynchronous operations that existed before the # temp loop was created. # This can't proceed within the temp loop p1 <- req_perform_promise(request_test("/delay/:secs", secs = 0.25)) later::with_temp_loop({ # You can create an async response with explicit pool=NULL, but it can't # proceed until the temp loop is over p2 <- req_perform_promise(request_test("/get"), pool = NULL) # You can create an async response with explicit pool=pool, and it can # proceed as long as that pool was first used inside of the temp loop p3 <- req_perform_promise(request_test("/get"), pool = curl::new_pool()) # You can't create an async response in the temp loop without explicitly # specifying a pool expect_snapshot( p4 <- req_perform_promise(request_test("/get")), error = TRUE ) # Like I said, you can create this, but it won't work until we get back # outside the temp loop expect_null(extract_promise(p2, timeout = 1)) # This works fine inside the temp loop, because its pool was first used # inside expect_equal(resp_status(extract_promise(p3, timeout = 1)), 200) }) # These work fine now that we're back outside the temp loop expect_equal(resp_status(extract_promise(p1, timeout = 1)), 200) expect_equal(resp_status(extract_promise(p2, timeout = 1)), 200) }) test_that("mocking works", { req_200 <- request("https://ok") req_404 <- request("https://notok") local_mocked_responses(function(req) { if (req$url == "https://ok") { response() } else { response(404) } }) expect_equal( extract_promise(req_perform_promise(req_200)), response() ) expect_error( extract_promise(req_perform_promise(req_404)), class = "httr2_http_404" ) }) # otel ----------------------------------------------------------------------- test_that("tracing works as expected", { skip_if_not_installed("otelsdk") skip_on_os("windows") spans <- with_otel_record({ # A request with no URL (which shouldn't create a span). try(req_perform_promise(request("")), silent = TRUE) # A request with an HTTP error. p <- req_perform_promise(request_test("/status/:status", status = 404)) try(extract_promise(p), silent = TRUE) # A request with a curl error. with_mocked_bindings( { p <- req_perform_promise(request("http://127.0.0.1")) try(extract_promise(p), silent = TRUE) }, curl_fetch = function(...) abort("Failed to connect") ) # A request with no parent context. p <- req_perform_promise(request_test("/headers")) # A request nested inside a parent span. parent <- otel::start_span("parent", tracer = "test") otel::with_active_span(parent, { child <- req_perform_promise(request_test()) # Resolve the earlier request with a different session; this should not # affect its parent. resp <- extract_promise(p) }) extract_promise(child) parent$end() # Verify that context propagation works as expected. expect_true( "traceparent" %in% names(resp_body_json(resp)[["headers"]]) ) })[["traces"]] expect_length(spans, 5L) # And for requests with HTTP errors. expect_equal(spans[[1]]$status, "error") expect_equal(spans[[1]]$description, "Not Found") expect_equal(spans[[1]]$attributes$http.response.status_code, 404L) expect_equal(spans[[1]]$attributes$error.type, "404") # And for spans with curl errors. expect_equal(spans[[2]]$status, "error") expect_equal(spans[[2]]$attributes$error.type, "curl_error_couldnt_connect") # We should have attached the curl error as an event. expect_length(spans[[2]]$events, 1L) expect_equal(spans[[2]]$events[[1]]$name, "exception") # Verify that the spans for requests resolved later still have the parent # context in which they were submitted. expect_equal(spans[[3]]$parent, spans[[5]]$parent) expect_equal(spans[[4]]$parent, spans[[5]]$span_id) })