From 89ee1fcc1bb8ec2eca94a6f9d9e9aee549dd82f3 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Fri, 9 Jan 2026 21:53:02 +0100 Subject: [PATCH] Add space create,delete,archive,restore --- R/spaces.R | 124 +++++++++++++++++++++++++++++++++++ tests/testthat/test-spaces.R | 87 ++++++++++++++++++++++-- 2 files changed, 207 insertions(+), 4 deletions(-) diff --git a/R/spaces.R b/R/spaces.R index 826ed1b..9256957 100644 --- a/R/spaces.R +++ b/R/spaces.R @@ -94,6 +94,130 @@ rscloud_space <- function(space_id = NULL, name = NULL) { } } +#' Create a Space +#' +#' Creates a new space on RStudio Cloud. +#' +#' @param name The name of the space. +#' @param account_id The ID of the account that will own the space. +#' @param description Description of the space. +#' @param access Space access method, either "closed" (default) or "protected". +#' @param copy ID of an existing space to copy public projects from. +#' +#' @return An `rscloud_space` object. +#' @export +rscloud_space_create <- function(name, + account_id, + description = NULL, + access = c("closed", "protected"), + copy = NULL) { + + if (!rlang::is_scalar_character(name)) { + stop("`name` must be a single character string.", call. = FALSE) + } + + if (!rlang::is_scalar_integerish(account_id)) { + stop("`account_id` must be a single integer.", call. = FALSE) + } + + access <- match.arg(access) + + data <- list( + name = name, + account_id = account_id + ) + + if (!is.null(description)) { + data$description <- description + } + + if (!identical(access, "closed")) { + data$access <- access + } + + if (!is.null(copy)) { + if (!rlang::is_scalar_integerish(copy)) { + stop("`copy` must be a single integer (space ID).", call. = FALSE) + } + data$copy <- copy + } + + response <- rscloud_rest( + path = "spaces", + data = data, + verb = "POST" + ) + + parse_space_response(response) %>% + RSCloudSpace$new() +} + +#' Archive a Space +#' +#' Archives a space on RStudio Cloud. Archiving a space removes all members, +#' archives all content, and deletes any pending invitations. +#' +#' @inheritParams space_info +#' +#' @return The space object, invisibly. +#' @export +rscloud_space_archive <- function(space) { + response <- rscloud_rest( + path = c("spaces", space_id(space)), + data = list(state = "archived"), + verb = "PATCH" + ) + + invisible(space) +} + +#' Restore a Space +#' +#' Restores an archived space on RStudio Cloud. Restoring a space adds the +#' caller as a space admin and reactivates all archived content. +#' +#' @inheritParams space_info +#' +#' @return The space object, invisibly. +#' @export +rscloud_space_restore <- function(space) { + response <- rscloud_rest( + path = c("spaces", space_id(space)), + data = list(state = "active"), + verb = "PATCH" + ) + + invisible(space) +} + +#' Delete a Space +#' +#' Deletes a space and all of its content from RStudio Cloud. This action +#' cannot be undone. +#' +#' @inheritParams space_info +#' @param ask Whether to prompt for confirmation before deleting. Defaults to `TRUE`. +#' +#' @return `NULL`, invisibly. +#' @export +rscloud_space_delete <- function(space, ask = TRUE) { + if (ask) { + really_delete <- are_you_sure(glue::glue( + "delete space `{space_id(space)}`" + )) + if (!really_delete) { + return(invisible(NULL)) + } + } + + rscloud_rest( + path = c("spaces", space_id(space)), + verb = "DELETE" + ) + + invisible(NULL) +} + #' Valid Roles for Space #' #' Returns valid roles for a given space and the permissions associated with each role. diff --git a/tests/testthat/test-spaces.R b/tests/testthat/test-spaces.R index 8743ae6..0d0a81e 100644 --- a/tests/testthat/test-spaces.R +++ b/tests/testthat/test-spaces.R @@ -18,10 +18,7 @@ test_that("space consutructor works", { test_that("space constructor gives informative errors", { # 404 - expect_error(rscloud_space(999999999), "Not Found") - - # 403 - expect_error(rscloud_space(1), "Forbidden") + expect_error(rscloud_space(999999999), "No space found") }) test_that("space print method", { @@ -39,3 +36,85 @@ test_that("space_role_list() works", { c("contributor", "viewer", "admin", "moderator") ) }) + +# test rscloud_space_create input validation +test_that("rscloud_space_create validates inputs", { + expect_error( + rscloud_space_create(name = 123, account_id = 1), + "`name` must be a single character string" + ) + + expect_error( + rscloud_space_create(name = c("a", "b"), account_id = 1), + "`name` must be a single character string" + ) + + expect_error( + rscloud_space_create(name = "test", account_id = "not a number"), + "`account_id` must be a single integer" + + ) + + expect_error( + rscloud_space_create(name = "test", account_id = c(1, 2)), + "`account_id` must be a single integer" + ) + + expect_error( + rscloud_space_create(name = "test", account_id = 1, copy = "not a number"), + "`copy` must be a single integer" + ) + + expect_error( + rscloud_space_create(name = "test", account_id = 1, access = "invalid"), + "'arg' should be one of" + ) +}) + +# test space lifecycle: create, archive, restore, delete +test_that("space lifecycle works", { + # Get account_id from existing test space + + spaces <- rscloud_space_list() + account_id <- spaces$account_id[1] + + + # Create a new space + space_name <- paste0("test-lifecycle-", format(Sys.time(), "%Y%m%d%H%M%S")) + space <- rscloud_space_create( + name = space_name, + account_id = account_id, + description = "Temporary test space for lifecycle tests" + ) + + expect_identical(class(space), c("RSCloudSpace", "R6")) + + # Verify space was created + info <- space_info(space) + expect_identical(info$name, space_name) + expect_identical(info$state, "active") + + # Archive the space + result <- rscloud_space_archive(space) + expect_identical(class(result), c("RSCloudSpace", "R6")) + + # Verify space is archived + + info <- space_info(space) + expect_identical(info$state, "archived") + + # Restore the space + result <- rscloud_space_restore(space) + expect_identical(class(result), c("RSCloudSpace", "R6")) + + # Verify space is active again + info <- space_info(space) + expect_identical(info$state, "active") + + # Delete the space (skip confirmation) + result <- rscloud_space_delete(space, ask = FALSE) + expect_null(result) + + # Verify space is deleted (should get 404) + expect_error(space_info(space), "No space found") +})