diff --git a/NAMESPACE b/NAMESPACE index c04ae9b2..9f14a7b9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,9 +7,11 @@ S3method(api_build,op_base_connect) S3method(api_build,op_head) S3method(as.data.frame,connect_integration_list) S3method(as.data.frame,connect_list_hits) +S3method(as.data.frame,connect_users) S3method(as.data.frame,tbl_connect) S3method(as_tibble,connect_integration_list) S3method(as_tibble,connect_list_hits) +S3method(as_tibble,connect_users) S3method(connect_vars,op_base) S3method(connect_vars,op_single) S3method(connect_vars,tbl_connect) @@ -115,6 +117,7 @@ export(get_usage_shiny) export(get_usage_static) export(get_user_permission) export(get_users) +export(get_users_list) export(get_vanity_url) export(get_vanity_urls) export(get_variant) diff --git a/R/connect.R b/R/connect.R index 0631c895..aefb1b43 100644 --- a/R/connect.R +++ b/R/connect.R @@ -495,9 +495,10 @@ Connect <- R6::R6Class( # users ----------------------------------------------- #' @description Get user details. - #' @param guid The user GUID. + #' @param guid The user GUID or a `connect_user` object. user = function(guid) { - self$GET(v1_url("users", guid)) + guid <- get_user_guid(guid) + prepend_class(self$GET(v1_url("users", guid)), "connect_user") }, #' @description Get users. @@ -527,7 +528,11 @@ Connect <- R6::R6Class( user_role = user_role, account_status = account_status ) - self$GET(path, query = query) + res <- self$GET(path, query = query) + if (!is.null(res$results)) { + res$results <- lapply(res$results, prepend_class, "connect_user") + } + res }, #' @description Get remote users. @@ -585,8 +590,9 @@ Connect <- R6::R6Class( }, #' @description Lock a user. - #' @param user_guid User GUID. - users_lock = function(user_guid) { + #' @param user User GUID or a `connect_user` object. + users_lock = function(user) { + user_guid <- get_user_guid(user) path <- v1_url("users", user_guid, "lock") message(path) self$POST( @@ -596,8 +602,9 @@ Connect <- R6::R6Class( }, #' @description Unlock a user. - #' @param user_guid User GUID. - users_unlock = function(user_guid) { + #' @param user User GUID or a `connect_user` object. + users_unlock = function(user) { + user_guid <- get_user_guid(user) path <- v1_url("users", user_guid, "lock") self$POST( path = path, @@ -606,9 +613,10 @@ Connect <- R6::R6Class( }, #' @description Update a user. - #' @param user_guid User GUID. + #' @param user User GUID or a `connect_user` object. #' @param ... User fields. - users_update = function(user_guid, ...) { + users_update = function(user, ...) { + user_guid <- get_user_guid(user) path <- v1_url("users", user_guid) self$PUT( path = path, @@ -641,16 +649,18 @@ Connect <- R6::R6Class( #' @description Add a group member. #' @param group_guid The group GUID. - #' @param user_guid The user GUID. - group_member_add = function(group_guid, user_guid) { + #' @param user The user GUID or a `connect_user` object. + group_member_add = function(group_guid, user) { + user_guid <- get_user_guid(user) path <- v1_url("groups", group_guid, "members") self$POST(path, body = list(user_guid = user_guid)) }, #' @description Remove a group member. #' @param group_guid The group GUID. - #' @param user_guid The user GUID. - group_member_remove = function(group_guid, user_guid) { + #' @param user The user GUID or a `connect_user` object. + group_member_remove = function(group_guid, user) { + user_guid <- get_user_guid(user) path <- v1_url("groups", group_guid, "members", user_guid) self$DELETE(path) }, diff --git a/R/content.R b/R/content.R index 355e21d0..e0ddb6d0 100644 --- a/R/content.R +++ b/R/content.R @@ -219,10 +219,14 @@ Content <- R6::R6Class( self$connect$GET(url) }, #' @description Add a principal to the ACL for this content. - #' @param principal_guid GUID for the target user or group. + #' @param principal_guid GUID for the target user or group. When + #' `principal_type = "user"`, can also be a `connect_user` object. #' @param principal_type Acting on user or group. #' @param role The kind of content access. permissions_add = function(principal_guid, principal_type, role) { + if (principal_type == "user") { + principal_guid <- get_user_guid(principal_guid) + } url <- v1_url("content", self$content$guid, "permissions") self$connect$POST( url, @@ -235,10 +239,14 @@ Content <- R6::R6Class( }, #' @description Alter a principal in the ACL for this content. #' @param id The target identifier. - #' @param principal_guid GUID for the target user or group. + #' @param principal_guid GUID for the target user or group. When + #' `principal_type = "user"`, can also be a `connect_user` object. #' @param principal_type Acting on user or group. #' @param role The kind of content access. permissions_update = function(id, principal_guid, principal_type, role) { + if (principal_type == "user") { + principal_guid <- get_user_guid(principal_guid) + } url <- v1_url("content", self$content$guid, "permissions", id) self$connect$PUT( url, @@ -954,7 +962,6 @@ set_run_as <- function(content, run_as, run_as_current_user = FALSE) { return(content) } - #' Delete Content #' #' Delete a content item. WARNING: This action deletes all history, configuration, @@ -1007,8 +1014,8 @@ content_delete <- function(content, force = FALSE) { #' @param content An R6 content item #' @param ... Settings up update that are passed along to Posit Connect #' @param access_type One of "all", "logged_in", or "acl" -#' @param owner_guid The GUID of a user who is a publisher, so that they can -#' become the new owner of the content +#' @param owner The GUID of a user who is a publisher, so that they can +#' become the new owner of the content. Can also be a `connect_user` object. #' #' @return An R6 content item #' @@ -1040,7 +1047,8 @@ content_update_access_type <- function( #' @rdname content_update #' @export -content_update_owner <- function(content, owner_guid) { +content_update_owner <- function(content, owner) { + owner_guid <- get_user_guid(owner) content_update(content = content, owner_guid = owner_guid) } @@ -1103,7 +1111,6 @@ unlock_content <- function(content) { return(content) } - #' Verify Content Name #' #' Ensures that a content name fits the specifications / requirements of Posit @@ -1178,7 +1185,6 @@ delete_bundle <- function(content, bundle_id) { return(content) } - #' Content permissions #' #' Get or set content permissions for a content item @@ -1199,8 +1205,12 @@ delete_bundle <- function(content, bundle_id) { #' This makes it easier to find / isolate this record. #' #' @param content An R6 content object -#' @param guid The guid associated with either a user (for `content_add_user`) or group (for `content_add_group`) -#' @param role The role to assign to a user. Either "viewer" or "owner." Defaults to "viewer" +#' @param user The guid associated with either a user (for `content_add_user`) +#' or group (for `content_add_group`). Can also be a list of `connect_user` +#' objects. +#' @param guid The guid associated with a group. +#' @param role The role to assign to a user. Either "viewer" or "owner." +#' Defaults to "viewer" #' @param add_owner Optional. Whether to include the owner in returned #' permission sets. Default is TRUE. The owner will have an NA_character_ #' permission "id" @@ -1209,10 +1219,11 @@ delete_bundle <- function(content, bundle_id) { #' @rdname permissions #' @family content functions #' @export -content_add_user <- function(content, guid, role = c("viewer", "owner")) { +content_add_user <- function(content, user, role = c("viewer", "owner")) { validate_R6_class(content, "Content") role <- .define_role(role) + guid <- purrr::map_chr(user, get_user_guid) purrr::map(guid, ~ .content_add_permission_impl(content, "user", .x, role)) return(content) @@ -1278,8 +1289,9 @@ content_add_group <- function(content, guid, role = c("viewer", "owner")) { #' @rdname permissions #' @export -content_delete_user <- function(content, guid) { +content_delete_user <- function(content, user) { validate_R6_class(content, "Content") + guid <- purrr::map_chr(user, get_user_guid) purrr::map( guid, ~ .content_delete_permission_impl( @@ -1331,8 +1343,9 @@ content_delete_group <- function(content, guid) { #' @rdname permissions #' @export -get_user_permission <- function(content, guid, add_owner = TRUE) { +get_user_permission <- function(content, user, add_owner = TRUE) { validate_R6_class(content, "Content") + guid <- get_user_guid(user) res <- .get_permission(content, "user", guid, add_owner = add_owner) if (length(res) > 0) { return(res[[1]]) @@ -1361,7 +1374,6 @@ get_group_permission <- function(content, guid) { } } - #' @rdname permissions #' @export get_content_permissions <- function(content, add_owner = TRUE) { diff --git a/R/get.R b/R/get.R index 01e41f06..cd0affb9 100644 --- a/R/get.R +++ b/R/get.R @@ -13,8 +13,8 @@ #' value (boolean OR). When `NULL` (the default), results are not filtered. #' -#' @return -#' A tibble with the following columns: +#' @return For `get_users_list`, a list of objects of type `"connect_user"` which +#' contain the following information: #' #' * `email`: The user's email #' * `username`: The user's username @@ -33,6 +33,10 @@ #' * `locked`: Whether or not the user is locked #' * `guid`: The user's GUID, or unique identifier, in UUID RFC4122 format #' +#' For `get_users`, a data frame with the same fields as `get_users_list`. +#' +#' For `get_user`, a single `"connect_user"` object. +#' #' @details #' Please see https://docs.posit.co/connect/api/#get-/v1/users for more information. #' @@ -41,7 +45,7 @@ #' library(connectapi) #' client <- connect() #' -#' # Get all users +#' # Get all users as a data frame #' get_users(client) #' #' # Get all licensed users @@ -49,10 +53,33 @@ #' #' # Get all users who are administrators or publishers #' get_users(client, user_role = c("administrator", "publisher")) +#' +#' # Get users as a list +#' users_list <- get_users_list(client) #' } #' #' @export -get_users <- function( +get_users <- function(src, + page_size = 500, + prefix = NULL, + limit = Inf, + user_role = NULL, + account_status = NULL) { + as.data.frame( + get_users_list( + src, + page_size, + prefix, + limit, + user_role, + account_status + ) + ) +} + +#' @rdname get_users +#' @export +get_users_list <- function( src, page_size = 500, prefix = NULL, @@ -72,10 +99,23 @@ get_users <- function( ), limit = limit ) + return(prepend_class(res, "connect_users")) +} - out <- parse_connectapi_typed(res, connectapi_ptypes$users) +#' @param guid user GUID +#' @rdname get_users +get_user <- function(src, guid) { + src$user(guid) +} - return(out) +#' @export +as.data.frame.connect_users <- function(x, ...) { + parse_connectapi_typed(x, connectapi_ptypes$users) +} + +#' @export +as_tibble.connect_users <- function(x, ...) { + parse_connectapi_typed(x, connectapi_ptypes$users) } #' Get information about content on the Posit Connect server diff --git a/R/user.R b/R/user.R index 90b2122b..a4d415b6 100644 --- a/R/user.R +++ b/R/user.R @@ -32,3 +32,28 @@ user_guid_from_username <- function(client, username) { return(res[[1]]$guid) } } + +#' Extract User GUID +#' +#' Helper function to extract a user GUID from either a character string or a +#' `connect_user` object. +#' +#' @param user Either a character string containing a user GUID or a +#' `connect_user` object (as returned by `Connect$user()` or `Connect$users()`) +#' +#' @return A character string containing the user GUID +#' +#' @keywords internal +get_user_guid <- function(user) { + if (is.character(user)) { + return(user) + } else if (inherits(user, "connect_user")) { + if (!is.null(user$guid)) { + return(user$guid) + } else { + stop("connect_user object does not contain a guid field") + } + } else { + stop("user must be either a character string (GUID) or a connect_user object") + } +} diff --git a/R/utils.R b/R/utils.R index 85e89b4e..be60518d 100644 --- a/R/utils.R +++ b/R/utils.R @@ -256,3 +256,9 @@ message_if_not_testing <- function(...) { message(...) } } + +# Prepends a new class to a given objec +prepend_class <- function(x, class) { + class(x) <- c(class, class(x)) + x +} diff --git a/man/Content.Rd b/man/Content.Rd index e8b45a1b..8e222bce 100644 --- a/man/Content.Rd +++ b/man/Content.Rd @@ -334,7 +334,8 @@ Add a principal to the ACL for this content. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{principal_guid}}{GUID for the target user or group.} +\item{\code{principal_guid}}{GUID for the target user or group. When +\code{principal_type = "user"}, can also be a \code{connect_user} object.} \item{\code{principal_type}}{Acting on user or group.} @@ -357,7 +358,8 @@ Alter a principal in the ACL for this content. \describe{ \item{\code{id}}{The target identifier.} -\item{\code{principal_guid}}{GUID for the target user or group.} +\item{\code{principal_guid}}{GUID for the target user or group. When +\code{principal_type = "user"}, can also be a \code{connect_user} object.} \item{\code{principal_type}}{Acting on user or group.} diff --git a/man/PositConnect.Rd b/man/PositConnect.Rd index 241b0c9a..47c2267f 100644 --- a/man/PositConnect.Rd +++ b/man/PositConnect.Rd @@ -767,7 +767,7 @@ Get user details. \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{guid}}{The user GUID.} +\item{\code{guid}}{The user GUID or a \code{connect_user} object.} } \if{html}{\out{
}} } @@ -883,13 +883,13 @@ Create a remote user. \subsection{Method \code{users_lock()}}{ Lock a user. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Connect$users_lock(user_guid)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Connect$users_lock(user)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{user_guid}}{User GUID.} +\item{\code{user}}{User GUID or a \code{connect_user} object.} } \if{html}{\out{
}} } @@ -900,13 +900,13 @@ Lock a user. \subsection{Method \code{users_unlock()}}{ Unlock a user. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Connect$users_unlock(user_guid)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Connect$users_unlock(user)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{user_guid}}{User GUID.} +\item{\code{user}}{User GUID or a \code{connect_user} object.} } \if{html}{\out{
}} } @@ -917,13 +917,13 @@ Unlock a user. \subsection{Method \code{users_update()}}{ Update a user. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Connect$users_update(user_guid, ...)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Connect$users_update(user, ...)}\if{html}{\out{
}} } \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{user_guid}}{User GUID.} +\item{\code{user}}{User GUID or a \code{connect_user} object.} \item{\code{...}}{User fields.} } @@ -974,7 +974,7 @@ Get group members. \subsection{Method \code{group_member_add()}}{ Add a group member. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Connect$group_member_add(group_guid, user_guid)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Connect$group_member_add(group_guid, user)}\if{html}{\out{
}} } \subsection{Arguments}{ @@ -982,7 +982,7 @@ Add a group member. \describe{ \item{\code{group_guid}}{The group GUID.} -\item{\code{user_guid}}{The user GUID.} +\item{\code{user}}{The user GUID or a \code{connect_user} object.} } \if{html}{\out{
}} } @@ -993,7 +993,7 @@ Add a group member. \subsection{Method \code{group_member_remove()}}{ Remove a group member. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Connect$group_member_remove(group_guid, user_guid)}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{Connect$group_member_remove(group_guid, user)}\if{html}{\out{
}} } \subsection{Arguments}{ @@ -1001,7 +1001,7 @@ Remove a group member. \describe{ \item{\code{group_guid}}{The group GUID.} -\item{\code{user_guid}}{The user GUID.} +\item{\code{user}}{The user GUID or a \code{connect_user} object.} } \if{html}{\out{
}} } diff --git a/man/VariantR6.Rd b/man/VariantR6.Rd index be0cc4a6..cdf922d6 100644 --- a/man/VariantR6.Rd +++ b/man/VariantR6.Rd @@ -39,7 +39,6 @@ Other R6 classes: \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-Variant-get_variant}{\code{Variant$get_variant()}} \item \href{#method-Variant-get_variant_remote}{\code{Variant$get_variant_remote()}} \item \href{#method-Variant-new}{\code{Variant$new()}} \item \href{#method-Variant-send_mail}{\code{Variant$send_mail()}} @@ -90,19 +89,12 @@ Other R6 classes: }} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-Variant-get_variant}{}}} -\subsection{Method \code{get_variant()}}{ -Get the underlying variant data. -\subsection{Usage}{ -\if{html}{\out{
}}\preformatted{Variant$get_variant()}\if{html}{\out{
}} -} - -} -\if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-Variant-get_variant_remote}{}}} \subsection{Method \code{get_variant_remote()}}{ +Get the underlying variant data. + + Get and store the (remote) variant data. \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Variant$get_variant_remote()}\if{html}{\out{
}} diff --git a/man/VariantSchedule.Rd b/man/VariantSchedule.Rd index ee961e1a..d7c23e29 100644 --- a/man/VariantSchedule.Rd +++ b/man/VariantSchedule.Rd @@ -81,7 +81,6 @@ Other R6 classes:
  • connectapi::Variant$get_subscribers()
  • connectapi::Variant$get_url()
  • connectapi::Variant$get_url_rev()
  • -
  • connectapi::Variant$get_variant()
  • connectapi::Variant$get_variant_remote()
  • connectapi::Variant$job()
  • connectapi::Variant$jobs()
  • diff --git a/man/VariantTask.Rd b/man/VariantTask.Rd index 4f23bd9e..316a53a1 100644 --- a/man/VariantTask.Rd +++ b/man/VariantTask.Rd @@ -80,7 +80,6 @@ Other R6 classes:
  • connectapi::Variant$get_subscribers()
  • connectapi::Variant$get_url()
  • connectapi::Variant$get_url_rev()
  • -
  • connectapi::Variant$get_variant()
  • connectapi::Variant$get_variant_remote()
  • connectapi::Variant$job()
  • connectapi::Variant$jobs()
  • diff --git a/man/content_update.Rd b/man/content_update.Rd index ef1254e8..a5e49522 100644 --- a/man/content_update.Rd +++ b/man/content_update.Rd @@ -10,7 +10,7 @@ content_update(content, ...) content_update_access_type(content, access_type = c("all", "logged_in", "acl")) -content_update_owner(content, owner_guid) +content_update_owner(content, owner) } \arguments{ \item{content}{An R6 content item} @@ -19,8 +19,8 @@ content_update_owner(content, owner_guid) \item{access_type}{One of "all", "logged_in", or "acl"} -\item{owner_guid}{The GUID of a user who is a publisher, so that they can -become the new owner of the content} +\item{owner}{The GUID of a user who is a publisher, so that they can +become the new owner of the content. Can also be a \code{connect_user} object.} } \value{ An R6 content item diff --git a/man/get_user_guid.Rd b/man/get_user_guid.Rd new file mode 100644 index 00000000..b69472f0 --- /dev/null +++ b/man/get_user_guid.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/user.R +\name{get_user_guid} +\alias{get_user_guid} +\title{Extract User GUID} +\usage{ +get_user_guid(user) +} +\arguments{ +\item{user}{Either a character string containing a user GUID or a +\code{connect_user} object (as returned by \code{Connect$user()} or \code{Connect$users()})} +} +\value{ +A character string containing the user GUID +} +\description{ +Helper function to extract a user GUID from either a character string or a +\code{connect_user} object. +} +\keyword{internal} diff --git a/man/get_users.Rd b/man/get_users.Rd index 456b8afa..abb497bb 100644 --- a/man/get_users.Rd +++ b/man/get_users.Rd @@ -2,6 +2,8 @@ % Please edit documentation in R/get.R \name{get_users} \alias{get_users} +\alias{get_users_list} +\alias{get_user} \title{Get user information from the Posit Connect server} \usage{ get_users( @@ -12,6 +14,17 @@ get_users( user_role = NULL, account_status = NULL ) + +get_users_list( + src, + page_size = 500, + prefix = NULL, + limit = Inf, + user_role = NULL, + account_status = NULL +) + +get_user(src, guid) } \arguments{ \item{src}{The source object} @@ -30,9 +43,12 @@ The filter is case insensitive.} \item{account_status}{Optionally filter by account status ("locked", "licensed", "inactive"). Pass a vector of multiple statuses to match any value (boolean OR). When \code{NULL} (the default), results are not filtered.} + +\item{guid}{user GUID} } \value{ -A tibble with the following columns: +For \code{get_users_list}, a list of objects of type \code{"connect_user"} which +contain the following information: \itemize{ \item \code{email}: The user's email \item \code{username}: The user's username @@ -51,6 +67,10 @@ through an email. This feature is unique to password authentication. \item \code{locked}: Whether or not the user is locked \item \code{guid}: The user's GUID, or unique identifier, in UUID RFC4122 format } + +For \code{get_users}, a data frame with the same fields as \code{get_users_list}. + +For \code{get_user}, a single \code{"connect_user"} object. } \description{ Get user information from the Posit Connect server @@ -63,7 +83,7 @@ Please see https://docs.posit.co/connect/api/#get-/v1/users for more information library(connectapi) client <- connect() -# Get all users +# Get all users as a data frame get_users(client) # Get all licensed users @@ -71,6 +91,9 @@ get_users(client, account_status = "licensed") # Get all users who are administrators or publishers get_users(client, user_role = c("administrator", "publisher")) + +# Get users as a list +users_list <- get_users_list(client) } } diff --git a/man/permissions.Rd b/man/permissions.Rd index 1af0aff9..0db517a4 100644 --- a/man/permissions.Rd +++ b/man/permissions.Rd @@ -12,15 +12,15 @@ \alias{get_content_permissions} \title{Content permissions} \usage{ -content_add_user(content, guid, role = c("viewer", "owner")) +content_add_user(content, user, role = c("viewer", "owner")) content_add_group(content, guid, role = c("viewer", "owner")) -content_delete_user(content, guid) +content_delete_user(content, user) content_delete_group(content, guid) -get_user_permission(content, guid, add_owner = TRUE) +get_user_permission(content, user, add_owner = TRUE) get_my_permission(content, add_owner = TRUE) @@ -31,9 +31,14 @@ get_content_permissions(content, add_owner = TRUE) \arguments{ \item{content}{An R6 content object} -\item{guid}{The guid associated with either a user (for \code{content_add_user}) or group (for \code{content_add_group})} +\item{user}{The guid associated with either a user (for \code{content_add_user}) +or group (for \code{content_add_group}). Can also be a list of \code{connect_user} +objects.} -\item{role}{The role to assign to a user. Either "viewer" or "owner." Defaults to "viewer"} +\item{role}{The role to assign to a user. Either "viewer" or "owner." +Defaults to "viewer"} + +\item{guid}{The guid associated with a group.} \item{add_owner}{Optional. Whether to include the owner in returned permission sets. Default is TRUE. The owner will have an NA_character_ diff --git a/tests/integrated/test-get.R b/tests/integrated/test-get.R index 0e41106c..860bf909 100644 --- a/tests/integrated/test-get.R +++ b/tests/integrated/test-get.R @@ -8,7 +8,6 @@ cont1_content <- NULL test_that("get_users works", { users <- get_users(test_conn_1) - expect_s3_class(users, c("tbl_df", "tbl", "data.frame")) expect_equal( purrr::map_chr(vctrs::vec_ptype(users), typeof), diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index f7e8e434..466a91d6 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -144,6 +144,32 @@ with_mock_api({ "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions" ) }) + + test_that("Content$permissions_add() accepts connect_user object for users", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066") + user_guid <- "a01792e3-2e67-402e-99af-be04a48da074" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_POST( + item$permissions_add(user, "user", "viewer"), + "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions", + '{"principal_guid":"a01792e3-2e67-402e-99af-be04a48da074","principal_type":"user","role":"viewer"}' + ) + }) + + test_that("Content$permissions_update() accepts connect_user object for users", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066") + user_guid <- "a01792e3-2e67-402e-99af-be04a48da074" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_PUT( + item$permissions_update(94, user, "user", "editor"), + "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions/94", + '{"principal_guid":"a01792e3-2e67-402e-99af-be04a48da074","principal_type":"user","role":"editor"}' + ) + }) }) without_internet({ @@ -467,6 +493,55 @@ test_that("get_content_packages() gets packages", { }) }) +with_mock_api({ + test_that("content_add_user() accepts connect_user object", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066") + user_guid <- "a01792e3-2e67-402e-99af-be04a48da074" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_POST( + suppressMessages(content_add_user(item, user)), + "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions" + ) + + # Test with list of connect_user objects + user2 <- structure(list(guid = "b02892e3-2e67-402e-99af-be04a48da075"), class = "connect_user") + expect_POST( + suppressMessages(content_add_user(item, list(user, user2))), + "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066/permissions" + ) + }) + + test_that("get_user_permission() accepts connect_user object", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066") + user_guid <- "a01792e3-2e67-402e-99af-be04a48da074" + user <- structure(list(guid = user_guid), class = "connect_user") + + # Test with GUID string - should work + perm1 <- suppressMessages(get_user_permission(item, user_guid)) + + # Test with connect_user object - should also work and return same result + perm2 <- suppressMessages(get_user_permission(item, user)) + + # Both should return the same permission (or NULL if not found) + expect_equal(perm1, perm2) + }) + + test_that("content_update_owner() accepts connect_user object", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + item <- content_item(con, "f2f37341-e21d-3d80-c698-a935ad614066") + user_guid <- "a01792e3-2e67-402e-99af-be04a48da074" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_PATCH( + suppressMessages(content_update_owner(item, user)), + "https://connect.example/__api__/v1/content/f2f37341-e21d-3d80-c698-a935ad614066" + ) + }) +}) + # content search ---- with_mock_dir("2025.09.0", { diff --git a/tests/testthat/test-groups.R b/tests/testthat/test-groups.R index 957cd3b3..6fa5319c 100644 --- a/tests/testthat/test-groups.R +++ b/tests/testthat/test-groups.R @@ -34,6 +34,30 @@ without_internet({ "https://connect.example/__api__/v1/groups/remote?limit=500" ) }) + + test_that("group_member_add() accepts connect_user object", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + group_guid <- "a6fb5cea-0123-4567-89ab-cdef01234567" + user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_POST( + client$group_member_add(group_guid, user), + "https://connect.example/__api__/v1/groups/a6fb5cea-0123-4567-89ab-cdef01234567/members" + ) + }) + + test_that("group_member_remove() accepts connect_user object", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + group_guid <- "a6fb5cea-0123-4567-89ab-cdef01234567" + user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_DELETE( + client$group_member_remove(group_guid, user), + "https://connect.example/__api__/v1/groups/a6fb5cea-0123-4567-89ab-cdef01234567/members/20a79ce3-6e87-4522-9faf-be24228800a4" # nolint + ) + }) }) with_mock_dir("2024.08.0", { diff --git a/tests/testthat/test-users.R b/tests/testthat/test-users.R index 23f45c5b..60f6f455 100644 --- a/tests/testthat/test-users.R +++ b/tests/testthat/test-users.R @@ -1,3 +1,47 @@ +# Tests for get_user_guid() helper function ---- + +test_that("get_user_guid() works with character GUID", { + guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + expect_equal(get_user_guid(guid), guid) +}) + +test_that("get_user_guid() works with connect_user object", { + user <- structure( + list( + guid = "20a79ce3-6e87-4522-9faf-be24228800a4", + username = "carlos12", + first_name = "Carlos", + last_name = "User" + ), + class = "connect_user" + ) + expect_equal(get_user_guid(user), "20a79ce3-6e87-4522-9faf-be24228800a4") +}) + +test_that("get_user_guid() errors with invalid input", { + expect_error( + get_user_guid(123), + "user must be either a character string \\(GUID\\) or a connect_user object" + ) + expect_error( + get_user_guid(list(name = "test")), + "user must be either a character string \\(GUID\\) or a connect_user object" + ) +}) + +test_that("get_user_guid() errors when connect_user has no guid field", { + user <- structure( + list(username = "carlos12"), + class = "connect_user" + ) + expect_error( + get_user_guid(user), + "connect_user object does not contain a guid field" + ) +}) + +# Tests for Connect client methods accepting connect_user objects ---- + with_mock_api({ test_that("we can retrieve the users list", { con <- Connect$new(server = "https://connect.example", api_key = "fake") @@ -6,6 +50,13 @@ with_mock_api({ expect_equal(nrow(users), 3) }) + test_that("we can retrieve the users list as list", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + users <- get_users_list(con) + expect_s3_class(users, "connect_users") + expect_equal(length(users), 3) + }) + test_that("we can retrieve a user by id", { con <- Connect$new(server = "https://connect.example", api_key = "fake") a_user <- con$user("20a79ce3-6e87-4522-9faf-be24228800a4") @@ -13,6 +64,14 @@ with_mock_api({ expect_type(a_user, "list") expect_equal(a_user$first_name, "Carlos") }) + + test_that("Connect$user() accepts connect_user object", { + con <- Connect$new(server = "https://connect.example", api_key = "fake") + user <- con$user("20a79ce3-6e87-4522-9faf-be24228800a4") + same_user <- con$user(user) + expect_equal(same_user$guid, "20a79ce3-6e87-4522-9faf-be24228800a4") + expect_equal(same_user$first_name, "Carlos") + }) }) without_internet({ @@ -43,4 +102,52 @@ without_internet({ "https://connect.example/__api__/v1/users/remote?prefix=A%20user%20name" ) }) + + test_that("users_lock() accepts connect_user object or GUID string", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_POST( + client$users_lock(user_guid), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock" + ) + + expect_POST( + client$users_lock(user), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock" + ) + }) + + test_that("users_unlock() accepts connect_user object or GUID string", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_POST( + client$users_unlock(user_guid), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock" + ) + + expect_POST( + client$users_unlock(user), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4/lock" + ) + }) + + test_that("users_update() accepts connect_user object or GUID string", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + user_guid <- "20a79ce3-6e87-4522-9faf-be24228800a4" + user <- structure(list(guid = user_guid), class = "connect_user") + + expect_PUT( + client$users_update(user_guid, first_name = "New Name"), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4" + ) + + expect_PUT( + client$users_update(user, first_name = "New Name"), + "https://connect.example/__api__/v1/users/20a79ce3-6e87-4522-9faf-be24228800a4" + ) + }) })