tests/testthat/test-client-connectCloud.R

test_that("awaitCompletion", {
  skip_if_not_installed("webfakes")

  revision_app <- webfakes::new_app()
  revision_app$use(webfakes::mw_json())
  revision_app$get("/revisions/:id", function(req, res) {
    res$set_status(200L)$send_json(
      list(
        id = I(req$params$id),
        content_id = "content789",
        publish_result = "success",
        status = "published",
        url = "https://example.posit.cloud/content/123",
        publish_error_details = NULL
      ),
      auto_unbox = TRUE
    )
  })
  app <- webfakes::new_app_process(revision_app)
  service <- parseHttpUrl(app$url())

  authInfo <- list(
    server = "connect.posit.cloud",
    name = "some-user",
    username = "some-user",
    accountId = "123",
    accessToken = "current-token",
    refreshToken = "refresh-token"
  )
  client <- connectCloudClient(service, authInfo)

  # test successful completion
  result <- client$awaitCompletion("rev123")
  expect_true(result$success)
  expect_equal(
    result$url,
    "https://connect.posit.cloud/some-user/content/content789"
  )
  expect_null(result$error)
})

test_that("awaitCompletion handles failure", {
  skip_if_not_installed("webfakes")

  revision_app <- webfakes::new_app()
  revision_app$use(webfakes::mw_json())
  revision_app$get("/revisions/:id", function(req, res) {
    res$set_status(200L)$send_json(
      list(
        id = I(req$params$id),
        content_id = "content789",
        publish_result = "failure",
        status = "published",
        url = NULL,
        publish_error_details = "Deployment failed due to missing dependencies"
      ),
      auto_unbox = TRUE
    )
  })
  app <- webfakes::new_app_process(revision_app)
  service <- parseHttpUrl(app$url())

  authInfo <- list(
    server = "connect.posit.cloud",
    name = "some-user",
    username = "some-user",
    accountId = "123",
    accessToken = "current-token",
    refreshToken = "refresh-token"
  )
  client <- connectCloudClient(service, authInfo)

  # test failure case
  result <- client$awaitCompletion("rev456")
  expect_false(result$success)
  expect_equal(
    result$url,
    "https://connect.posit.cloud/some-user/content/content789"
  )
  expect_equal(result$error, "Deployment failed due to missing dependencies")
})

test_that("awaitCompletion handles failure with logs", {
  skip_if_not_installed("webfakes")

  # Mock revision API that returns failure with log channel
  cloudApiApp <- webfakes::new_app()
  cloudApiApp$use(webfakes::mw_json())
  cloudApiApp$get("/revisions/:id", function(req, res) {
    res$set_status(200L)$send_json(
      list(
        id = I(req$params$id),
        content_id = "content789",
        publish_result = "failure",
        status = "published",
        url = NULL,
        publish_error_details = "Deployment failed due to missing dependencies",
        publish_log_channel = "log-channel-123"
      ),
      auto_unbox = TRUE
    )
  })

  # Mock authorization API
  cloudApiApp$post("/authorization", function(req, res) {
    res$set_status(200L)$send_json(
      list(
        authorized = TRUE,
        token = "auth-token-xyz"
      ),
      auto_unbox = TRUE
    )
  })

  # Mock logs API
  logs_app <- webfakes::new_app()
  logs_app$use(webfakes::mw_json())
  logs_app$get("/v1/logs/:channel", function(req, res) {
    res$set_status(200L)$send_json(
      list(
        data = list(
          list(
            timestamp = 1234567890 * 1000000,
            message = "Starting deployment...",
            level = "info"
          ),
          list(
            timestamp = 1234567891 * 1000000,
            message = "Your app is busted!!",
            level = "error"
          )
        )
      ),
      auto_unbox = TRUE
    )
  })

  # Start the main app and logs app
  app <- webfakes::new_app_process(cloudApiApp)
  logs_app_process <- webfakes::new_app_process(logs_app)

  service <- parseHttpUrl(app$url())
  authInfo <- list(
    server = "connect.posit.cloud",
    name = "some-user",
    username = "some-user",
    accountId = "123",
    accessToken = "current-token",
    refreshToken = "refresh-token"
  )

  # Mock connectCloudUrls and connectCloudLogsClient
  local_mocked_bindings(
    connectCloudUrls = function() {
      list(logs = logs_app_process$url(), ui = "https://connect.posit.cloud")
    },
    connectCloudLogsClient = function() {
      list(
        getLogs = function(logChannel, authToken) {
          logsUrl <- logs_app_process$url()
          service <- parseHttpUrl(paste0(logsUrl, "/v1"))

          authInfo <- list(
            accessToken = authToken
          )

          path <- paste0(
            "/logs/",
            logChannel,
            "?traversal_direction=backward&limit=1500"
          )
          response <- GET(service, authInfo, path)
          response
        }
      )
    }
  )

  client <- connectCloudClient(service, authInfo)

  # Test failure case with logs - capture stderr output
  stderr_output <- capture.output(
    {
      result <- client$awaitCompletion("rev456")
    },
    type = "message"
  )

  # Check the result object
  expect_false(result$success)
  expect_equal(
    result$url,
    "https://connect.posit.cloud/some-user/content/content789"
  )
  expect_equal(result$error, "Deployment failed due to missing dependencies")

  # Check that logs were printed to stderr
  stderr_text <- paste(stderr_output, collapse = "\n")
  info <- paste0("stderr_text was:\n", stderr_text)

  expect_match(stderr_text, "Begin Publishing Log")
  expect_match(stderr_text, "End Publishing Log")
  expect_match(stderr_text, "Starting deployment...")
  expect_match(stderr_text, "Your app is busted!!")
  expect_match(stderr_text, "INFO:")
  expect_match(stderr_text, "ERROR:")
})

test_that("withTokenRefreshRetry passes through successful requests", {
  skip_if_not_installed("webfakes")

  # Mock a successful API call
  mock_request_fn <- function(service, authInfo, path) {
    list(success = TRUE, data = "test response")
  }

  service <- list(host = "example.com", port = 443, protocol = "https")
  authInfo <- list(
    server = "connect.posit.cloud",
    name = "some-user",
    username = "some-user",
    accountId = "123",
    accessToken = "current-token",
    refreshToken = "refresh-token"
  )
  client <- connectCloudClient(service, authInfo)

  result <- client$withTokenRefreshRetry(
    mock_request_fn,
    "/test"
  )

  expect_equal(result$success, TRUE)
  expect_equal(result$data, "test response")
})

test_that("withTokenRefreshRetry handles 401 with successful token refresh", {
  skip_if_not_installed("webfakes")

  call_count <- 0
  mock_request_fn <- function(service, authInfo, path) {
    call_count <<- call_count + 1
    if (call_count == 1) {
      # First call fails with 401
      err <- structure(
        list(message = "HTTP 401"),
        class = c("rsconnect_http_401", "rsconnect_http", "error", "condition")
      )
      stop(err)
    } else {
      # Second call succeeds
      list(success = TRUE, data = "success after refresh")
    }
  }

  # Mock cloudAuthClient and registerAccount
  register_called <- FALSE
  local_mocked_bindings(
    cloudAuthClient = function() {
      list(
        exchangeToken = function(request) {
          expect_equal(request$grant_type, "refresh_token")
          expect_equal(request$refresh_token, "refresh-token")
          list(
            access_token = "new-access-token",
            refresh_token = "new-refresh-token"
          )
        }
      )
    },
    registerAccount = function(
      server,
      name,
      accountId,
      accessToken,
      refreshToken
    ) {
      register_called <<- TRUE
      expect_equal(server, "connect.posit.cloud")
      expect_equal(name, "test-user")
      expect_equal(accountId, "123")
      expect_equal(accessToken, "new-access-token")
      expect_equal(refreshToken, "new-refresh-token")
    }
  )

  service <- list(host = "example.com", port = 443, protocol = "https")
  authInfo <- list(
    server = "connect.posit.cloud",
    name = "test-user",
    accountId = "123",
    accessToken = "current-token",
    refreshToken = "refresh-token"
  )
  client <- connectCloudClient(service, authInfo)

  result <- client$withTokenRefreshRetry(mock_request_fn, "/test")

  expect_equal(result$success, TRUE)
  expect_equal(result$data, "success after refresh")
  expect_equal(call_count, 2)
  expect_true(register_called)
})

Try the rsconnect package in your browser

Any scripts or data that you put into this service are public.

rsconnect documentation built on Nov. 5, 2025, 5:29 p.m.