Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions R/spaces.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
87 changes: 83 additions & 4 deletions tests/testthat/test-spaces.R
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand All @@ -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")
})
Loading