nealrichardson / httptest

A Test Environment for HTTP Requests in R
https://enpiar.com/r/httptest/
Other
79 stars 10 forks source link

Unexpected '<' in POST response #9

Closed MarkEdmondson1234 closed 6 years ago

MarkEdmondson1234 commented 6 years ago

Hi Neal, I have a failing test occur when the response includes an auth_token = <environment> field. The generated file is shown below - it is a POST batch from googleanalyticsR which is generated with this code ( link to file on GitHub)

The POST response returns other responses within it that may be GET, DELETE etc - its a way to send many API calls at once. Perhaps it is unexpected for your mock functions though.

library(httptest)
.mockPaths("..")

library(googleAnalyticsR)

accountId <- 54019251
webPropId <- "UA-54019251-4"
ga_id <- 106249469

context("Batch API Mocking")

test_that("Record requests if online", {
  skip_if_disconnected()
  skip_if_no_env_auth(local_auth)

  ## test reqs
  capture_requests(
    {
      google_analytics(c(ga_id2, ga_id),
                       start = "2015-07-30", end = "2015-10-01",
                       dimensions=c('medium'),
                       metrics = c('sessions'),
                       sort = "ga:sessions",
                       multi_account_batching = TRUE)
  })
})

with_mock_API({

  test_that("v3 multi account batching without flag", {

    skip_on_cran()
    multi <- google_analytics(c(ga_id, ga_id2),
                              start = "2015-07-31", end = "2015-10-01",
                              dimensions=c('medium'),
                              metrics = c('sessions'),
                              sort = "ga:sessions")

    expect_length(multi, 2)

    expect_s3_class(multi[[1]], "data.frame")
    expect_s3_class(multi[[2]], "data.frame")

  })

})

The offending response is below:

structure(list(url = "https://www.googleapis.com/batch/analytics/v3", 
    status_code = 200L, headers = structure(list(vary = "Origin", 
        vary = "X-Origin", `content-type` = "multipart/mixed; boundary=batch_83THWe0jLVQ_AAufwBbHho4", 
        `content-encoding` = "gzip", date = "Thu, 19 Oct 2017 08:43:52 GMT", 
        expires = "Thu, 19 Oct 2017 08:43:52 GMT", `cache-control` = "private, max-age=0", 
        `x-content-type-options` = "nosniff", `x-frame-options` = "SAMEORIGIN", 
        `x-xss-protection` = "1; mode=block", server = "GSE", 
        `alt-svc` = "quic=\":443\"; ma=2592000; v=\"39,38,37,35\"", 
        `transfer-encoding` = "chunked"), .Names = c("vary", 
    "vary", "content-type", "content-encoding", "date", "expires", 
    "cache-control", "x-content-type-options", "x-frame-options", 
    "x-xss-protection", "server", "alt-svc", "transfer-encoding"
    ), class = c("insensitive", "list")), all_headers = list(
        structure(list(status = 200L, version = "HTTP/1.1", headers = structure(list(
            vary = "Origin", vary = "X-Origin", `content-type` = "multipart/mixed; boundary=batch_83THWe0jLVQ_AAufwBbHho4", 
            `content-encoding` = "gzip", date = "Thu, 19 Oct 2017 08:43:52 GMT", 
            expires = "Thu, 19 Oct 2017 08:43:52 GMT", `cache-control` = "private, max-age=0", 
            `x-content-type-options` = "nosniff", `x-frame-options` = "SAMEORIGIN", 
            `x-xss-protection` = "1; mode=block", server = "GSE", 
            `alt-svc` = "quic=\":443\"; ma=2592000; v=\"39,38,37,35\"", 
            `transfer-encoding` = "chunked"), .Names = c("vary", 
        "vary", "content-type", "content-encoding", "date", "expires", 
        "cache-control", "x-content-type-options", "x-frame-options", 
        "x-xss-protection", "server", "alt-svc", "transfer-encoding"
        ), class = c("insensitive", "list"))), .Names = c("status", 
        "version", "headers"))), cookies = structure(list(domain = logical(0), 
        flag = logical(0), path = logical(0), secure = logical(0), 
        expiration = structure(numeric(0), class = c("POSIXct", 
        "POSIXt")), name = logical(0), value = logical(0)), .Names = c("domain", 
    "flag", "path", "secure", "expiration", "name", "value"), row.names = integer(0), class = "data.frame"), 
    content = as.raw(c(0x2d, 0x2d, 0x62, 0x61, 0x74, 0x63, 0x68, 
    0x5f, 0x38, 0x33, 0x54, 0x48, 0x57, 0x65, 0x30, 0x6a, 0x4c, 
....
    0x51, 0x5f, 0x41, 0x41, 0x75, 0x66, 0x77, 0x42, 0x62, 0x48, 
    0x68, 0x6f, 0x34, 0x2d, 0x2d, 0x0d, 0x0a)), date = structure(1508402632, class = c("POSIXct", 
    "POSIXt"), tzone = "GMT"), times = structure(c(0, 4e-05, 
    4.2e-05, 0.000102, 0.333098, 0.333141), .Names = c("redirect", 
    "namelookup", "connect", "pretransfer", "starttransfer", 
    "total")), request = structure(list(method = "POST", url = "https://www.googleapis.com/batch/analytics/v3", 
        headers = structure(c("application/json, text/xml, application/xml, */*", 
        "gzip", "multipart/mixed; boundary=gar_batch", "REDACTED"
        ), .Names = c("Accept", "Accept-Encoding", "Content-Type", 
        "Authorization")), fields = NULL, options = structure(list(
            http_version = 0, post = TRUE, postfieldsize = 620L, 
            postfields = as.raw(c(0x2d, 0x2d, 0x67, 0x61, 0x72, 
            0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x0d, 0x0a, 0x43, 
......
            0x68, 0x2d, 0x2d)), useragent = "googleAuthR/0.5.1.9005 (gzip)"), .Names = c("http_version", 
        "post", "postfieldsize", "postfields", "useragent")), 
        auth_token = <environment>, output = structure(list(), class = c("write_memory", 
        "write_function"))), .Names = c("method", "url", "headers", 
    "fields", "options", "auth_token", "output"), class = "request")), .Names = c("url", 
"status_code", "headers", "all_headers", "cookies", "content", 
"date", "times", "request"), class = "response")

When trying to debug I tried to load the file contents via copy-pasting the created file's content and got the same error.

mocked <- COPY-PASTE
#...
#"post", "postfieldsize", "postfields", "useragent")), 
#+                                                                                                                                                                                                                                                                      #auth_token = <environment>, output = structure(list(), class = c("write_memory", 
#Error: unexpected '<' in "                                                                                                                                                                                                #"
#>                                                                                                                                                                                                                                                                                                                                       #"write_function"))), .Names = c("method", "url", "headers", 
#Error: unexpected ')' in "                                                                                                                                                                                                #"
#>       

Also just for future reference, is the expected behaviour to be able to recreate the response from the copy-pasted file contents?

nealrichardson commented 6 years ago

Thanks for reporting. This should be fixed in https://github.com/nealrichardson/httptest/commit/570ffffa7332eb45b5618be749a5acfd85f3da0f. Please let me know if that addresses the issue for you. Working to get a CRAN release up for this.

nealrichardson commented 6 years ago

And yes, to your last question, you can load the full R response from the recorded file. You don't have to copy and paste--just response <- source(filename)$value. That's what with_mock_API does, in fact: https://github.com/nealrichardson/httptest/blob/master/R/mock-api.R#L38

nealrichardson commented 6 years ago

Also, for an immediate workaround, you can just delete the auth_token = <environment> from the bad .R file that was written out, and then the rest of your code should work. The fix I pushed is only on the recording side (it will purge the auth_token from the request written out), so next time you record, you'll get clean files.

MarkEdmondson1234 commented 6 years ago

That works now, thanks!