tests/testthat/test-redact.R

d <- tempfile()

with_mock_api({
  # Auth headers aren't recorded
  capture_while_mocking(simplify = FALSE, path = d, {
    a <- GET("api/", add_headers(`Authorization` = "Bearer token"))
  })
  test_that("The response has the real request header", {
    expect_equal(a$request$headers[["Authorization"]], "Bearer token")
  })
  test_that("But the mock file does not", {
    expect_false(any(grepl("Bearer token", readLines(file.path(d, "api.R")))))
  })
  test_that("And the redacted .R mock can be loaded", {
    with_mock_path(d, {
      b <- GET("api/", add_headers(`Authorization` = "Bearer token"))
    })
    expect_equal(content(b), content(a))
  })

  # Request cookies aren't recorded
  capture_while_mocking(simplify = FALSE, path = d, {
    cooks <- GET("http://httpbin.org/cookies", set_cookies(token = "12345"))
  })
  test_that("redact_cookies: the response has the cookie set in the request", {
    expect_identical(cooks$request$options$cookie, "token=12345")
  })
  test_that("redact_cookies removes cookies from request in the mock file", {
    cooksfile <- readLines(file.path(d, "httpbin.org", "cookies.R"))
    expect_false(any(grepl("token=12345", cooksfile)))
  })
  test_that("And the redacted .R mock can be loaded", {
    with_mock_path(d, {
      cooksb <- GET("http://httpbin.org/cookies", set_cookies(token = "12345"))
    })
    expect_equal(content(cooksb), content(cooks))
  })

  # redact_cookies from response
  capture_while_mocking(simplify = FALSE, path = d, {
    c2 <- GET("http://httpbin.org/cookies/set", query = list(token = 12345))
  })
  test_that("redact_cookies: the response has the set-cookie in the response", {
    # Note the "all_headers": the request did a 302 redirect
    expect_identical(
      c2$all_headers[[1]]$headers[["set-cookie"]],
      "token=12345; Path=/"
    )
    expect_identical(c2$cookies$value, "12345")
  })
  test_that("redact_cookies removes set-cookies from response in the mock file", {
    # Note that "token=12345" appears in the request URL because of how
    # httpbin works. But normally your cookie wouldn't be in the URL.
    # Of course, if you wanted to sanitize URLs too, you could write your
    # own custom redacting function.
    expect_false(any(grepl(
      '"token=12345',
      readLines(file.path(d, "httpbin.org", "cookies", "set-5b2631.R"))
    )))
    expect_length(
      grep(
        "REDACTED",
        readLines(file.path(d, "httpbin.org", "cookies", "set-5b2631.R"))
      ),
      2
    )
  })
  test_that("And when loading that .R mock, the redacted value doesn't appear", {
    with_mock_path(d, {
      c2b <- GET("http://httpbin.org/cookies/set", query = list(token = 12345))
    })
    expect_identical(
      c2b$all_headers[[1]]$headers[["set-cookie"]],
      "REDACTED"
    )
    expect_identical(c2b$cookies$value, "REDACTED")
  })

  # another redact_cookies with POST example.com/login
  capture_while_mocking(simplify = FALSE, path = d, {
    login <- POST("http://example.com/login",
      body = list(username = "password"), encode = "json"
    )
  })
  test_that("redact_cookies: the response has the set-cookie in the response", {
    # Note the "all_headers": the request did a 302 redirect
    expect_true(grepl(
      "token=12345",
      login$all_headers[[1]]$headers[["set-cookie"]]
    ))
    expect_true(grepl(
      "token=12345",
      login$headers[["set-cookie"]]
    ))
    expect_identical(login$cookies$value, "12345")
  })
  test_that("redact_cookies removes set-cookies from response in the mock file", {
    # Unlike other example, token=12345 isn't in the URL
    expect_false(any(grepl(
      "12345",
      readLines(file.path(d, "example.com", "login-712027-POST.R"))
    )))
    expect_length(
      grep(
        "REDACTED",
        readLines(file.path(d, "example.com", "login-712027-POST.R"))
      ),
      3
    )
  })
  test_that("And when loading that .R mock, the redacted value doesn't appear", {
    with_mock_path(d, {
      loginb <- POST("http://example.com/login",
        body = list(username = "password"), encode = "json"
      )
    })
    expect_identical(
      loginb$all_headers[[1]]$headers[["set-cookie"]],
      "REDACTED"
    )
    expect_identical(loginb$headers[["set-cookie"]], "REDACTED")
    expect_identical(loginb$cookies$value, "REDACTED")
  })

  # HTTP auth credentials aren't recorded
  capture_while_mocking(simplify = FALSE, path = d, {
    pwauth <- GET(
      "http://httpbin.org/basic-auth/user/passwd",
      authenticate("user", "passwd")
    )
  })
  test_that("redact_http_auth: the request has the user:pw set", {
    expect_identical(pwauth$request$options$userpwd, "user:passwd")
  })
  test_that("redact_http_auth removes user:pw from request in the mock file", {
    expect_false(any(grepl(
      "user:passwd",
      readLines(file.path(d, "httpbin.org", "basic-auth", "user", "passwd.R"))
    )))
  })
  test_that("And the redacted .R mock can be loaded", {
    with_mock_path(d, {
      pwauthb <- GET(
        "http://httpbin.org/basic-auth/user/passwd",
        authenticate("user", "passwd")
      )
    })
    expect_equal(content(pwauthb), content(pwauth))
  })

  # OAuth credentials aren't recorded
  # Example token copied from a test in httr
  token <- Token2.0$new(
    app = oauth_app("x", "y", "z"),
    endpoint = oauth_endpoints("google"),
    credentials = list(access_token = "ofNoArms")
  )
  token$params$as_header <- TRUE
  capture_while_mocking(simplify = FALSE, path = d, {
    oauth <- GET("api/object1/", config(token = token))
  })
  test_that("The response has the 'auth_token' object'", {
    expect_s3_class(oauth$request$auth_token, "Token2.0")
  })

  test_that("But the mock doesn't have the auth_token", {
    oauthfile <- readLines(file.path(d, "api", "object1.R"))
    expect_false(any(grepl("auth_token", oauthfile)))
  })
  test_that("And the .R mock can be loaded", {
    with_mock_path(d, {
      oauthb <- GET("api/object1/", config(token = token))
    })
    expect_equal(content(oauthb), content(oauth))
  })

  # Custom redacting function
  my_redactor <- function(response) {
    # Proof that you can alter other parts of the response/mock
    response$url <- response$request$url <- "http://example.com/fakeurl"
    # Proof that you can alter the response body
    cleaner <- function(x) gsub("loaded", "changed", x)
    response <- within_body_text(response, cleaner)
    return(response)
  }
  with_redactor(
    my_redactor,
    capture_while_mocking(simplify = FALSE, path = d, {
      r <- GET("http://example.com/get")
    })
  )
  test_that("The real request is not affected by the redactor", {
    expect_identical(r$url, "http://example.com/get")
    expect_identical(content(r), list(loaded = TRUE))
  })
  test_that("But the mock file gets written to the modified path with altered content", {
    # Use replace=TRUE to make sure that "." isn't in the search path.
    # We're checking that the original request doesn't have a mock,
    # but of course we made it from a mock in the working directory
    with_mock_path(d, replace = TRUE, {
      expect_GET(
        GET("http://example.com/get"),
        "http://example.com/get"
      )
      expect_error(alt <- GET("http://example.com/fakeurl"), NA)
      expect_identical(content(alt), list(changed = TRUE))
    })
  })

  a <- GET("api/", add_headers(`Authorization` = "Bearer token"))
  test_that("gsub_response", {
    asub <- gsub_response(a, "api", "OTHER")
    expect_identical(asub$url, "OTHER/")
    expect_identical(content(asub), list(value = "OTHER/object1/"))
  })
  test_that("as.redactor", {
    a2 <- prepare_redactor(~ gsub_response(., "api", "OTHER"))(a)
    expect_identical(content(a2), list(value = "OTHER/object1/"))
  })

  loc <- GET("http://httpbin.org/response-headers",
    query = list(Location = "http://httpbin.org/status/201")
  )
  loc_sub <- gsub_response(
    loc, "http://httpbin.org/status/201",
    "http://httpbin.org/status/404"
  )
  test_that("gsub_response touches Location header", {
    expect_identical(
      loc_sub$headers$location,
      "http://httpbin.org/status/404"
    )
    expect_identical(
      loc_sub$all_headers[[1]]$headers$location,
      "http://httpbin.org/status/404"
    )
    expect_identical(
      content(loc_sub)$Location,
      "http://httpbin.org/status/404"
    )
  })
  test_that("gsub_response handles URL encoding", {
    skip("TODO: handle URL escaping")
    expect_identical(
      loc_sub$url,
      "http://httpbin.org/response-headers?Location=http%3A%2F%2Fhttpbin.org%2Fstatus%2F404"
    )
  })
})


with_fake_http({
  expect_PUT(
    p <- PUT("http://httpbin.org/put",
      body = list(x = "A string", string = "Something else")
    )
  )
  req <- p$request
  # TODO: more gsub_request tests
  test_that("gsub_request on non-JSON post fields", {
    expect_identical(
      gsub_request(req, "string", "SECRET")$fields,
      list(x = "A SECRET", SECRET = "Something else")
    )
  })
})

test_that("chain_redactors", {
  f1 <- function(x) x * 4
  f2 <- ~ sum(c(., 3))
  f12 <- chain_redactors(list(f1, f2))
  f21 <- chain_redactors(list(f2, f1))
  expect_equal(f12(5), 23)
  expect_equal(f21(5), 32)
})

reset_redactors()
nealrichardson/httptest documentation built on Feb. 5, 2024, 12:35 a.m.