test_that("validates inputs", {
  expect_snapshot(error = TRUE, {
    req_perform_connection(1)
    req_perform_connection(request_test(), 1)
  })
})

test_that("correctly prepares request", {
  req <- request_test("/post") |> req_method("POST")
  expect_no_error(resp <- req_perform_connection(req))
  close(resp)
})

test_that("can read all data from a connection", {
  resp <- request_test("/stream-bytes/2048") |> req_perform_connection()
  withr::defer(close(resp))

  out <- resp_body_raw(resp)
  expect_length(out, 2048)
  expect_false(resp_has_body(resp))
})

test_that("reads body on error", {
  req <- local_app_request(function(req, res) {
    res$set_status(404L)$send_json(list(status = 404), auto_unbox = TRUE)
  })

  expect_error(req_perform_connection(req), class = "httr2_http_404")
  resp <- last_response()
  expect_equal(resp_body_json(resp), list(status = 404))
})

test_that("can retry a transient error", {
  req <- local_app_request(function(req, res) {
    if (res$app$locals$i == 1) {
      res$set_status(429)$set_header("retry-after", 0)$send_json(
        list(status = "waiting"),
        auto_unbox = TRUE
      )
    } else {
      res$send_json(list(status = "done"), auto_unbox = TRUE)
    }
  })
  req <- req_retry(req, max_tries = 2)

  cnd <- expect_condition(
    resp <- req_perform_connection(req),
    class = "httr2_retry"
  )
  expect_s3_class(cnd, "httr2_retry")
  expect_equal(cnd$tries, 1)
  expect_equal(cnd$delay, 0)

  expect_equal(last_response(), resp)
  expect_equal(resp_body_json(resp), list(status = "done"))
})

test_that("curl errors become errors", {
  local_mocked_bindings(open = function(...) abort("Failed to connect"))

  req <- request("http://127.0.0.1")
  expect_snapshot(req_perform_connection(req), error = TRUE)
  expect_error(req_perform_connection(req), class = "httr2_failure")

  # and captures request
  cnd <- catch_cnd(req_perform_connection(req), classes = "error")
  expect_equal(cnd$request, req_policies(req, connection = TRUE))

  # But last_response() is NULL
  expect_null(last_response())
})

test_that("correclty reports curl error with retries (#817)", {
  local_mocked_bindings(open = function(...) abort("Failed to connect"))

  req <- request("http://127.0.0.1") |>
    req_retry(max_tries = 2, retry_on_failure = TRUE, backoff = \(i) 0)

  expect_snapshot(req_perform_connection(req), error = TRUE)
})

test_that("mocking works", {
  req_200 <- request("https://ok")
  req_404 <- request("https://notok")

  local_mocked_responses(function(req) {
    expect_equal(req$policies$connection, TRUE)
    if (req$url == "https://ok") {
      conn <- rawConnection(charToRaw("a\nb\n"))
      response(body = StreamingBody$new(conn))
    } else {
      response(404)
    }
  })

  resp <- req_perform_connection(req_200)
  expect_equal(resp_stream_lines(resp), "a")
  expect_equal(resp_stream_lines(resp), "b")
  close(resp)

  expect_error(req_perform_connection(req_404), class = "httr2_http_404")
})


# StreamingBody --------------------------------------------------------------

test_that("validates its input", {
  expect_snapshot(error = TRUE, {
    StreamingBody$new(1)
  })
})

test_that("can access fdset", {
  skip_on_cran()

  con <- curl::curl(example_url('/drip'), open = 'rb')
  on.exit(close(con))

  body <- StreamingBody$new(con)
  expect_length(body$get_fdset()$reads, 1)
})

# 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_connection(request("")), silent = TRUE)

    # A regular request.
    resp <- req_perform_connection(request_test("/headers"))

    # Verify that context propagation works as expected.
    expect_true("traceparent" %in% names(resp_body_json(resp)[["headers"]]))
    close(resp)

    # A request with an HTTP error.
    try(
      req_perform_connection(request_test("/status/:status", status = 404)),
      silent = TRUE
    )

    # A request with basic credentials that we should redact.
    parsed <- url_parse(example_url())
    parsed$username <- "user"
    parsed$password <- "secret"
    resp <- req_perform_connection(request(url_build(parsed)))
    close(resp)

    # A request with a curl error.
    with_mocked_bindings(
      try(req_perform_connection(request("http://127.0.0.1")), silent = TRUE),
      curl_fetch = function(...) abort("Failed to connect")
    )

    # A request that triggers retries, generating three individual spans.
    request_test("/status/:status", status = 429) |>
      req_retry(max_tries = 3, backoff = ~0) |>
      req_perform_connection() |>
      try(silent = TRUE)
  })[["traces"]]

  expect_length(spans, 7L)

  # Validate the span for regular requests.
  expect_equal(spans[[1]]$status, "ok")
  expect_named(
    spans[[1]]$attributes,
    c(
      "http.response.status_code",
      "user_agent.original",
      "url.full",
      "server.address",
      "server.port",
      "http.request.method"
    ),
    ignore.order = TRUE
  )
  expect_equal(spans[[1]]$attributes$http.request.method, "GET")
  expect_equal(spans[[1]]$attributes$http.response.status_code, 200L)
  expect_equal(spans[[1]]$attributes$server.address, "127.0.0.1")
  expect_match(spans[[1]]$attributes$user_agent.original, "^httr2/")

  # And for requests with HTTP errors.
  expect_equal(spans[[2]]$status, "error")
  expect_equal(spans[[2]]$description, "Not Found")
  expect_equal(spans[[2]]$attributes$http.response.status_code, 404L)
  expect_equal(spans[[2]]$attributes$error.type, "404")

  # And for spans with redacted credentials.
  expect_match(
    spans[[3]]$attributes$url.full,
    regexp = "http://REDACTED:REDACTED@127.0.0.1:[0-9]+/",
  )

  # And for spans with curl errors.
  expect_equal(spans[[4]]$status, "error")
  expect_equal(spans[[4]]$attributes$error.type, "simpleError")

  # We should have attached the curl error as an event.
  expect_length(spans[[4]]$events, 1L)
  expect_equal(spans[[4]]$events[[1]]$name, "exception")

  # For spans with retries, we expect the parent context to be the same for
  # each span. It is important that they not be children of one another.
  expect_equal(spans[[5]]$parent, spans[[6]]$parent)
  expect_equal(spans[[6]]$parent, spans[[7]]$parent)
})
