diff --git a/.lintr b/.lintr index bb8d589..652b217 100644 --- a/.lintr +++ b/.lintr @@ -1,2 +1,2 @@ linters: linters_with_defaults(line_length_linter = line_length_linter(120)) -exclusions: list("_wip", "tests/testthat", "vignettes", "R/CONSTANTS.R" = list(object_name_linter = Inf), "R/aaa.R" = list(object_name_linter = Inf), "R/auth_utils.R" = list(object_name_linter = Inf), "R/utils.R" = list(object_name_linter = Inf)) +exclusions: list("_wip", "tests/testthat", "vignettes", "R/CONSTANTS.R" = list(object_name_linter = Inf, object_length_linter = Inf), "R/aaa.R" = list(object_name_linter = Inf), "R/auth_utils.R" = list(object_name_linter = Inf), "R/utils.R" = list(object_name_linter = Inf)) diff --git a/NAMESPACE b/NAMESPACE index 52943e7..1df58ce 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,11 +2,15 @@ export("%>%") export(HHMMSSmmm_to_ms) +export(add_default_record_to_session) export(assign_constants) export(assign_record_to_file) +export(check_duplicate_files_in_session) export(check_ssl_certs) +export(create_session) export(create_volume_record) export(delete_record_measure) +export(delete_session) export(delete_volume_record) export(disable_volume_category) export(download_folder_asset) @@ -72,6 +76,8 @@ export(login_db) export(logout_db) export(make_default_request) export(make_login_client) +export(patch_session) +export(remove_default_record_from_session) export(search_for_funder) export(search_for_tags) export(search_institutions) @@ -80,6 +86,7 @@ export(search_volumes) export(set_record_measure) export(set_volume_enabled_categories) export(unassign_record_from_file) +export(update_session) export(update_volume_record) export(whoami) importFrom(lifecycle,deprecated) diff --git a/R/CONSTANTS.R b/R/CONSTANTS.R index cc8b497..c246f41 100644 --- a/R/CONSTANTS.R +++ b/R/CONSTANTS.R @@ -35,6 +35,9 @@ API_VOLUME_RECORDS <- "/volumes/%s/records/" API_VOLUME_RECORD_DETAIL <- "/volumes/%s/records/%s/" API_RECORD_MEASURES <- "/volumes/%s/records/%s/measures/%s/" API_SESSION_DETAIL <- "/volumes/%s/sessions/%s/" +API_SESSION_ADD_DEFAULT_RECORD <- "/volumes/%s/sessions/%s/add-default-record/" +API_SESSION_REMOVE_DEFAULT_RECORD <- "/volumes/%s/sessions/%s/remove-default-record/" +API_SESSION_CHECK_DUPLICATE_FILES <- "/volumes/%s/sessions/%s/check-duplicate-files/" API_SESSION_FILES <- "/volumes/%s/sessions/%s/files/" API_SESSION_FILE_DETAIL <- "/volumes/%s/sessions/%s/files/%s/" API_SESSION_FILE_ASSIGN <- "/volumes/%s/sessions/%s/files/%s/assign-record/" diff --git a/R/add_default_record_to_session.R b/R/add_default_record_to_session.R new file mode 100644 index 0000000..97b87ea --- /dev/null +++ b/R/add_default_record_to_session.R @@ -0,0 +1,63 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Add a Default Record to a Session +#' +#' @description Attach a record to a session as a default record. Default +#' records apply to all files in the session unless overridden. The record +#' must either belong to the destination volume or be accessible to it via a +#' linked volume; the server enforces this and returns \code{403} otherwise. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param record_id Numeric record identifier. Must be a positive integer. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return \code{TRUE} if the record was successfully added, \code{FALSE} +#' otherwise. +#' +#' @seealso \code{\link{remove_default_record_from_session}} +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' add_default_record_to_session(vol_id = 1, session_id = 42, record_id = 101) +#' } +#' } +#' @export +add_default_record_to_session <- function( + vol_id = 1, + session_id, + record_id, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + assert_positive_integer(record_id, "record_id") + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + result <- perform_api_post( + path = sprintf(API_SESSION_ADD_DEFAULT_RECORD, vol_id, session_id), + body = list(record_id = record_id), + rq = rq, + vb = vb + ) + + if (is.null(result)) { + if (vb) { + message( + "Failed to add default record ", record_id, + " to session ", session_id, " in volume ", vol_id + ) + } + return(FALSE) + } + + TRUE +} diff --git a/R/api_utils.R b/R/api_utils.R index 58f780d..1c86cc0 100644 --- a/R/api_utils.R +++ b/R/api_utils.R @@ -339,6 +339,53 @@ perform_api_patch <- function(path, payload } +#' @noRd +# TODO: verify behavior against the live API. Mirrors `perform_api_patch`, +# but no existing wrapper currently issues PUT, so this helper is unexercised. +perform_api_put <- function(path, + body = list(), + rq = NULL, + vb = FALSE, + normalize = TRUE) { + request <- rq + if (is.null(request)) { + request <- databraryr::make_default_request() + } + + url <- paste0(DATABRARY_BASE_URL, ensure_leading_slash(path)) + request <- httr2::req_url(request, url) + request <- httr2::req_method(request, "PUT") + + if (!is.null(body) && length(body) > 0) { + request <- httr2::req_body_json(request, body) + } + + response <- tryCatch( + httr2::req_perform(request), + httr2_error = function(cnd) { + if (vb) { + message("PUT request failed for ", url, ": ", conditionMessage(cnd)) + } + NULL + } + ) + + if (is.null(response)) { + return(NULL) + } + + status <- httr2::resp_status(response) + if (status == 204L || !resp_has_body(response)) { + return(TRUE) + } + + payload <- httr2::resp_body_json(response) + if (isTRUE(normalize)) { + payload <- snake_case_list(payload) + } + payload +} + #' @noRd perform_api_delete <- function(path, rq = NULL, diff --git a/R/check_duplicate_files_in_session.R b/R/check_duplicate_files_in_session.R new file mode 100644 index 0000000..6e02c72 --- /dev/null +++ b/R/check_duplicate_files_in_session.R @@ -0,0 +1,85 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Check Whether Filenames Already Exist in a Session +#' +#' @description Ask the server which of the supplied filenames already +#' exist as files in the given session. Useful before bulk uploads to detect +#' name collisions in advance. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param filenames Character vector of filenames to check. Length must be +#' at least 1; each element must be a non-empty string. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return A \code{tibble} with columns \code{filename} (character) and +#' \code{exists} (logical), one row per input filename and in the same +#' order. Returns \code{NULL} if the request fails. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' check_duplicate_files_in_session( +#' vol_id = 1, +#' session_id = 42, +#' filenames = c("clip_001.mp4", "clip_002.mp4") +#' ) +#' } +#' } +#' @export +check_duplicate_files_in_session <- function( # nolint: object_length_linter. + vol_id = 1, + session_id, + filenames, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + + assertthat::assert_that( + is.character(filenames), + length(filenames) >= 1, + msg = "filenames must be a non-empty character vector" + ) + assertthat::assert_that( + !any(is.na(filenames)), + all(nzchar(trimws(filenames))), + msg = "filenames must not contain NA or empty strings" + ) + + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + # `as.list()` on a length-1 character would still serialize to a JSON array + # via httr2, but be explicit so the contract matches the server's expected + # `[...]` shape regardless of length. + body <- list(filenames = as.list(filenames)) + + result <- perform_api_post( + path = sprintf(API_SESSION_CHECK_DUPLICATE_FILES, vol_id, session_id), + body = body, + rq = rq, + vb = vb + ) + + if (is.null(result) || isTRUE(result)) { + if (vb) { + message( + "Failed to check duplicate filenames in session ", + session_id, " of volume ", vol_id + ) + } + return(NULL) + } + + tibble::tibble( + filename = vapply(result, function(r) as.character(r$filename), character(1)), + exists = vapply(result, function(r) isTRUE(r$exists), logical(1)) + ) +} diff --git a/R/create_session.R b/R/create_session.R new file mode 100644 index 0000000..4b7e968 --- /dev/null +++ b/R/create_session.R @@ -0,0 +1,177 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Coerce a date-like argument to an ISO "YYYY-MM-DD" string. +#' +#' Accepts a `Date` object or a length-1 character already in ISO form. +#' +#' @noRd +coerce_iso_date <- function(value, name) { + if (inherits(value, "Date")) { + assertthat::assert_that( + length(value) == 1, + msg = paste(name, "must have length 1") + ) + return(format(value, "%Y-%m-%d")) + } + assertthat::assert_that( + is.character(value), + length(value) == 1, + nzchar(trimws(value)), + msg = paste(name, "must be a Date or non-empty 'YYYY-MM-DD' string") + ) + parsed <- tryCatch(as.Date(value), error = function(e) NA) + assertthat::assert_that( + !is.na(parsed), + msg = paste(name, "must parse as a date (e.g. '2024-03-15')") + ) + format(parsed, "%Y-%m-%d") +} + +#' Create Session in Databrary Volume +#' +#' @description Create a new session in a Databrary volume. A session (a.k.a. +#' "slot") groups files and metadata for a single recording or testing event. +#' \code{name} is required and must be non-empty. Provide either a flat +#' \code{source_date} or a structured \code{date} list (with optional +#' \code{date_precision}) to record when the session occurred -- not both. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param name Display name for the session. Required, non-empty after trim. +#' @param release_level Optional release level for the session +#' (e.g. \code{"PRIVATE"}, \code{"SHARED"}, \code{"EXCERPTS"}, +#' \code{"PUBLIC"}). The server validates the choice. +#' @param source_date Optional session date. A length-1 \code{Date} object or +#' ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}. +#' @param date Optional structured date list with named fields \code{year}, +#' \code{month}, \code{day}, and optional \code{is_estimated} (logical). +#' Mutually exclusive with \code{source_date}. +#' @param date_precision Optional precision for \code{date}: e.g. +#' \code{"FULL"}, \code{"YEAR"}. Server validates the choice. +#' @param default_records Optional integer vector of record IDs to set as +#' default records on the new session. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return A list with the created session's metadata (same shape as +#' \code{\link{get_session_by_id}}), or \code{NULL} if creation fails. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' # Minimal session +#' create_session(vol_id = 1, name = "Pilot 01") +#' +#' # Session with a flat date +#' create_session( +#' vol_id = 1, +#' name = "Pilot 02", +#' source_date = as.Date("2024-03-15") +#' ) +#' +#' # Session with a structured date and default records +#' create_session( +#' vol_id = 1, +#' name = "Pilot 03", +#' date = list(year = 2024, month = 3, day = 15, is_estimated = FALSE), +#' date_precision = "FULL", +#' default_records = c(101, 102) +#' ) +#' } +#' } +#' @export +create_session <- function( + vol_id = 1, + name, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + + assertthat::assert_that(is.character(name), length(name) == 1) + assertthat::assert_that(nzchar(trimws(name)), msg = "name must not be empty") + + if (!is.null(release_level)) { + assertthat::assert_that( + is.character(release_level), + length(release_level) == 1, + nzchar(trimws(release_level)) + ) + } + + assertthat::assert_that( + is.null(source_date) || is.null(date), + msg = "Provide either source_date or date, not both." + ) + + if (!is.null(date)) { + assertthat::assert_that( + is.list(date), + !is.null(names(date)), + msg = "date must be a named list (e.g. list(year=, month=, day=))" + ) + } + + if (!is.null(date_precision)) { + assertthat::assert_that( + is.character(date_precision), + length(date_precision) == 1, + nzchar(trimws(date_precision)) + ) + } + + if (!is.null(default_records)) { + assertthat::assert_that( + is.numeric(default_records), + length(default_records) >= 1, + all(default_records >= 1), + all(default_records == floor(default_records)), + msg = "default_records must be a vector of positive integers" + ) + } + + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + body <- list(name = name) + + if (!is.null(release_level)) { + body$release_level <- release_level + } + if (!is.null(source_date)) { + body$source_date <- coerce_iso_date(source_date, "source_date") + } + if (!is.null(date)) { + body$date <- date + } + if (!is.null(date_precision)) { + body$date_precision <- date_precision + } + if (!is.null(default_records)) { + body$default_records <- as.list(as.integer(default_records)) + } + + session <- perform_api_post( + path = sprintf(API_VOLUME_SESSIONS, vol_id), + body = body, + rq = rq, + vb = vb + ) + + if (is.null(session)) { + if (vb) { + message("Failed to create session in volume ", vol_id) + } + return(NULL) + } + + session +} diff --git a/R/delete_session.R b/R/delete_session.R new file mode 100644 index 0000000..5c41bf5 --- /dev/null +++ b/R/delete_session.R @@ -0,0 +1,61 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Delete Session from Databrary Volume +#' +#' @description Delete (soft-delete) a session from a Databrary volume. The +#' session and its associated metadata are marked as deleted but not +#' permanently removed from the database. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return \code{TRUE} if the session was successfully deleted, \code{FALSE} +#' otherwise. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' # Delete a session +#' delete_session(vol_id = 1, session_id = 42) +#' +#' # Delete with verbose output +#' delete_session(vol_id = 1, session_id = 42, vb = TRUE) +#' } +#' } +#' @export +delete_session <- function( + vol_id = 1, + session_id, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + success <- perform_api_delete( + path = sprintf(API_SESSION_DETAIL, vol_id, session_id), + rq = rq, + vb = vb + ) + + if (!success) { + if (vb) { + message( + "Failed to delete session ", + session_id, + " from volume ", + vol_id + ) + } + } + + success +} diff --git a/R/patch_session.R b/R/patch_session.R new file mode 100644 index 0000000..76915d2 --- /dev/null +++ b/R/patch_session.R @@ -0,0 +1,159 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Partially Update a Session in Databrary Volume +#' +#' @description Sends a PATCH request to update selected fields of an existing +#' session. Only provided arguments are sent; omitted fields are left +#' unchanged on the server. Note that \code{default_records} is a full +#' replacement on the server -- supplying it overwrites the entire current set +#' of defaults; omit it to keep them. To change just one default record use +#' \code{\link{add_default_record_to_session}} or +#' \code{\link{remove_default_record_from_session}}. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param name Optional new session name. If provided, must be a non-empty +#' length-1 string. +#' @param release_level Optional release level (e.g. \code{"PRIVATE"}, +#' \code{"SHARED"}, \code{"EXCERPTS"}, \code{"PUBLIC"}). Server validates +#' the choice. +#' @param source_date Optional session date. A length-1 \code{Date} object or +#' ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}. +#' @param date Optional structured date list with named fields \code{year}, +#' \code{month}, \code{day}, and optional \code{is_estimated} (logical). +#' Mutually exclusive with \code{source_date}. +#' @param date_precision Optional precision for \code{date}: e.g. +#' \code{"FULL"}, \code{"YEAR"}. +#' @param default_records Optional integer vector of record IDs. The server +#' replaces the session's current default records with this set. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return A list with the updated session's metadata (same shape as +#' \code{\link{get_session_by_id}}), or \code{NULL} if the update fails or +#' no fields were provided. +#' +#' @seealso \code{\link{update_session}}, \code{\link{create_session}}, +#' \code{\link{add_default_record_to_session}}, +#' \code{\link{remove_default_record_from_session}} +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' # Rename a session +#' patch_session(vol_id = 1, session_id = 42, name = "Renamed session") +#' +#' # Update the date and precision +#' patch_session( +#' vol_id = 1, +#' session_id = 42, +#' date = list(year = 2024, month = 3, day = 15), +#' date_precision = "FULL" +#' ) +#' } +#' } +#' @export +patch_session <- function( + vol_id = 1, + session_id, + name = NULL, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + + if (!is.null(name)) { + assertthat::assert_that(is.character(name), length(name) == 1) + assertthat::assert_that( + nzchar(trimws(name)), + msg = "name must not be empty" + ) + } + + if (!is.null(release_level)) { + assertthat::assert_that( + is.character(release_level), + length(release_level) == 1, + nzchar(trimws(release_level)) + ) + } + + assertthat::assert_that( + is.null(source_date) || is.null(date), + msg = "Provide either source_date or date, not both." + ) + + if (!is.null(date)) { + assertthat::assert_that( + is.list(date), + !is.null(names(date)), + msg = "date must be a named list (e.g. list(year=, month=, day=))" + ) + } + + if (!is.null(date_precision)) { + assertthat::assert_that( + is.character(date_precision), + length(date_precision) == 1, + nzchar(trimws(date_precision)) + ) + } + + if (!is.null(default_records)) { + assertthat::assert_that( + is.numeric(default_records), + length(default_records) >= 1, + all(default_records >= 1), + all(default_records == floor(default_records)), + msg = "default_records must be a vector of positive integers" + ) + } + + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + body <- list() + if (!is.null(name)) body$name <- name + if (!is.null(release_level)) body$release_level <- release_level + if (!is.null(source_date)) body$source_date <- coerce_iso_date(source_date, "source_date") + if (!is.null(date)) body$date <- date + if (!is.null(date_precision)) body$date_precision <- date_precision + if (!is.null(default_records)) { + body$default_records <- as.list(as.integer(default_records)) + } + + if (length(body) == 0) { + if (vb) { + message("No fields provided to update for session ", session_id) + } + return(NULL) + } + + session <- perform_api_patch( + path = sprintf(API_SESSION_DETAIL, vol_id, session_id), + body = body, + rq = rq, + vb = vb + ) + + if (is.null(session)) { + if (vb) { + message( + "Failed to update session ", session_id, " in volume ", vol_id + ) + } + return(NULL) + } + + session +} diff --git a/R/remove_default_record_from_session.R b/R/remove_default_record_from_session.R new file mode 100644 index 0000000..2a83377 --- /dev/null +++ b/R/remove_default_record_from_session.R @@ -0,0 +1,62 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Remove a Default Record from a Session +#' +#' @description Detach a record from a session's default records. The record +#' must currently be a default record on the session, otherwise the server +#' returns \code{404}. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param record_id Numeric record identifier. Must be a positive integer. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return \code{TRUE} if the record was successfully removed, \code{FALSE} +#' otherwise. +#' +#' @seealso \code{\link{add_default_record_to_session}} +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' remove_default_record_from_session(vol_id = 1, session_id = 42, record_id = 101) +#' } +#' } +#' @export +remove_default_record_from_session <- function( # nolint: object_length_linter. + vol_id = 1, + session_id, + record_id, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + assert_positive_integer(record_id, "record_id") + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + result <- perform_api_post( + path = sprintf(API_SESSION_REMOVE_DEFAULT_RECORD, vol_id, session_id), + body = list(record_id = record_id), + rq = rq, + vb = vb + ) + + if (is.null(result)) { + if (vb) { + message( + "Failed to remove default record ", record_id, + " from session ", session_id, " in volume ", vol_id + ) + } + return(FALSE) + } + + TRUE +} diff --git a/R/update_session.R b/R/update_session.R new file mode 100644 index 0000000..c5461dc --- /dev/null +++ b/R/update_session.R @@ -0,0 +1,132 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Replace a Session in Databrary Volume (PUT) +#' +#' @description Sends a PUT request to fully replace a session's writable +#' fields. \code{name} is required (non-empty); other fields are optional and +#' default to server-side values when omitted (the underlying serializer +#' marks them \code{required=FALSE}). Use \code{\link{patch_session}} for +#' partial updates when you don't want full-replacement semantics. +#' +#' @param vol_id Target volume number. Must be a positive integer. +#' @param session_id Numeric session identifier. Must be a positive integer. +#' @param name New session name. Required, non-empty after trim. +#' @param release_level Optional release level (e.g. \code{"PRIVATE"}, +#' \code{"SHARED"}, \code{"EXCERPTS"}, \code{"PUBLIC"}). +#' @param source_date Optional session date. A length-1 \code{Date} object or +#' ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}. +#' @param date Optional structured date list with named fields \code{year}, +#' \code{month}, \code{day}, and optional \code{is_estimated} (logical). +#' Mutually exclusive with \code{source_date}. +#' @param date_precision Optional precision for \code{date}: e.g. +#' \code{"FULL"}, \code{"YEAR"}. +#' @param default_records Optional integer vector of record IDs. The server +#' replaces the session's current default records with this set; omit to +#' leave them unchanged. +#' @param rq An \code{httr2} request object. Defaults to \code{NULL}. +#' +#' @return A list with the updated session's metadata (same shape as +#' \code{\link{get_session_by_id}}), or \code{NULL} if the update fails. +#' +#' @seealso \code{\link{patch_session}}, \code{\link{create_session}} +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' # Rename a session via PUT +#' update_session(vol_id = 1, session_id = 42, name = "Replacement name") +#' } +#' } +#' @export +update_session <- function( + vol_id = 1, + session_id, + name, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) { + assert_positive_integer(vol_id, "vol_id") + assert_positive_integer(session_id, "session_id") + + assertthat::assert_that(is.character(name), length(name) == 1) + assertthat::assert_that(nzchar(trimws(name)), msg = "name must not be empty") + + if (!is.null(release_level)) { + assertthat::assert_that( + is.character(release_level), + length(release_level) == 1, + nzchar(trimws(release_level)) + ) + } + + assertthat::assert_that( + is.null(source_date) || is.null(date), + msg = "Provide either source_date or date, not both." + ) + + if (!is.null(date)) { + assertthat::assert_that( + is.list(date), + !is.null(names(date)), + msg = "date must be a named list (e.g. list(year=, month=, day=))" + ) + } + + if (!is.null(date_precision)) { + assertthat::assert_that( + is.character(date_precision), + length(date_precision) == 1, + nzchar(trimws(date_precision)) + ) + } + + if (!is.null(default_records)) { + assertthat::assert_that( + is.numeric(default_records), + length(default_records) >= 1, + all(default_records >= 1), + all(default_records == floor(default_records)), + msg = "default_records must be a vector of positive integers" + ) + } + + assertthat::assert_that(is.logical(vb), length(vb) == 1) + assertthat::assert_that(is.null(rq) || inherits(rq, "httr2_request")) + + body <- list(name = name) + if (!is.null(release_level)) body$release_level <- release_level + if (!is.null(source_date)) body$source_date <- coerce_iso_date(source_date, "source_date") + if (!is.null(date)) body$date <- date + if (!is.null(date_precision)) body$date_precision <- date_precision + if (!is.null(default_records)) { + body$default_records <- as.list(as.integer(default_records)) + } + + session <- perform_api_put( + path = sprintf(API_SESSION_DETAIL, vol_id, session_id), + body = body, + rq = rq, + vb = vb + ) + + if (is.null(session)) { + if (vb) { + message( + "Failed to replace session ", session_id, " in volume ", vol_id + ) + } + return(NULL) + } + + session +} diff --git a/man/add_default_record_to_session.Rd b/man/add_default_record_to_session.Rd new file mode 100644 index 0000000..49184f5 --- /dev/null +++ b/man/add_default_record_to_session.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_default_record_to_session.R +\name{add_default_record_to_session} +\alias{add_default_record_to_session} +\title{Add a Default Record to a Session} +\usage{ +add_default_record_to_session( + vol_id = 1, + session_id, + record_id, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{record_id}{Numeric record identifier. Must be a positive integer.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +\code{TRUE} if the record was successfully added, \code{FALSE} +otherwise. +} +\description{ +Attach a record to a session as a default record. Default +records apply to all files in the session unless overridden. The record +must either belong to the destination volume or be accessible to it via a +linked volume; the server enforces this and returns \code{403} otherwise. +} +\examples{ +\donttest{ +\dontrun{ +add_default_record_to_session(vol_id = 1, session_id = 42, record_id = 101) +} +} +} +\seealso{ +\code{\link{remove_default_record_from_session}} +} diff --git a/man/check_duplicate_files_in_session.Rd b/man/check_duplicate_files_in_session.Rd new file mode 100644 index 0000000..693a45b --- /dev/null +++ b/man/check_duplicate_files_in_session.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/check_duplicate_files_in_session.R +\name{check_duplicate_files_in_session} +\alias{check_duplicate_files_in_session} +\title{Check Whether Filenames Already Exist in a Session} +\usage{ +check_duplicate_files_in_session( + vol_id = 1, + session_id, + filenames, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{filenames}{Character vector of filenames to check. Length must be +at least 1; each element must be a non-empty string.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +A \code{tibble} with columns \code{filename} (character) and +\code{exists} (logical), one row per input filename and in the same +order. Returns \code{NULL} if the request fails. +} +\description{ +Ask the server which of the supplied filenames already +exist as files in the given session. Useful before bulk uploads to detect +name collisions in advance. +} +\examples{ +\donttest{ +\dontrun{ +check_duplicate_files_in_session( + vol_id = 1, + session_id = 42, + filenames = c("clip_001.mp4", "clip_002.mp4") +) +} +} +} diff --git a/man/create_session.Rd b/man/create_session.Rd new file mode 100644 index 0000000..7760b25 --- /dev/null +++ b/man/create_session.Rd @@ -0,0 +1,79 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/create_session.R +\name{create_session} +\alias{create_session} +\title{Create Session in Databrary Volume} +\usage{ +create_session( + vol_id = 1, + name, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{name}{Display name for the session. Required, non-empty after trim.} + +\item{release_level}{Optional release level for the session +(e.g. \code{"PRIVATE"}, \code{"SHARED"}, \code{"EXCERPTS"}, +\code{"PUBLIC"}). The server validates the choice.} + +\item{source_date}{Optional session date. A length-1 \code{Date} object or +ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}.} + +\item{date}{Optional structured date list with named fields \code{year}, +\code{month}, \code{day}, and optional \code{is_estimated} (logical). +Mutually exclusive with \code{source_date}.} + +\item{date_precision}{Optional precision for \code{date}: e.g. +\code{"FULL"}, \code{"YEAR"}. Server validates the choice.} + +\item{default_records}{Optional integer vector of record IDs to set as +default records on the new session.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +A list with the created session's metadata (same shape as +\code{\link{get_session_by_id}}), or \code{NULL} if creation fails. +} +\description{ +Create a new session in a Databrary volume. A session (a.k.a. +"slot") groups files and metadata for a single recording or testing event. +\code{name} is required and must be non-empty. Provide either a flat +\code{source_date} or a structured \code{date} list (with optional +\code{date_precision}) to record when the session occurred -- not both. +} +\examples{ +\donttest{ +\dontrun{ +# Minimal session +create_session(vol_id = 1, name = "Pilot 01") + +# Session with a flat date +create_session( + vol_id = 1, + name = "Pilot 02", + source_date = as.Date("2024-03-15") +) + +# Session with a structured date and default records +create_session( + vol_id = 1, + name = "Pilot 03", + date = list(year = 2024, month = 3, day = 15, is_estimated = FALSE), + date_precision = "FULL", + default_records = c(101, 102) +) +} +} +} diff --git a/man/delete_session.Rd b/man/delete_session.Rd new file mode 100644 index 0000000..9c073b2 --- /dev/null +++ b/man/delete_session.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delete_session.R +\name{delete_session} +\alias{delete_session} +\title{Delete Session from Databrary Volume} +\usage{ +delete_session(vol_id = 1, session_id, vb = options::opt("vb"), rq = NULL) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +\code{TRUE} if the session was successfully deleted, \code{FALSE} +otherwise. +} +\description{ +Delete (soft-delete) a session from a Databrary volume. The +session and its associated metadata are marked as deleted but not +permanently removed from the database. +} +\examples{ +\donttest{ +\dontrun{ +# Delete a session +delete_session(vol_id = 1, session_id = 42) + +# Delete with verbose output +delete_session(vol_id = 1, session_id = 42, vb = TRUE) +} +} +} diff --git a/man/patch_session.Rd b/man/patch_session.Rd new file mode 100644 index 0000000..fd92529 --- /dev/null +++ b/man/patch_session.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/patch_session.R +\name{patch_session} +\alias{patch_session} +\title{Partially Update a Session in Databrary Volume} +\usage{ +patch_session( + vol_id = 1, + session_id, + name = NULL, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{name}{Optional new session name. If provided, must be a non-empty +length-1 string.} + +\item{release_level}{Optional release level (e.g. \code{"PRIVATE"}, +\code{"SHARED"}, \code{"EXCERPTS"}, \code{"PUBLIC"}). Server validates +the choice.} + +\item{source_date}{Optional session date. A length-1 \code{Date} object or +ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}.} + +\item{date}{Optional structured date list with named fields \code{year}, +\code{month}, \code{day}, and optional \code{is_estimated} (logical). +Mutually exclusive with \code{source_date}.} + +\item{date_precision}{Optional precision for \code{date}: e.g. +\code{"FULL"}, \code{"YEAR"}.} + +\item{default_records}{Optional integer vector of record IDs. The server +replaces the session's current default records with this set.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +A list with the updated session's metadata (same shape as +\code{\link{get_session_by_id}}), or \code{NULL} if the update fails or +no fields were provided. +} +\description{ +Sends a PATCH request to update selected fields of an existing +session. Only provided arguments are sent; omitted fields are left +unchanged on the server. Note that \code{default_records} is a full +replacement on the server -- supplying it overwrites the entire current set +of defaults; omit it to keep them. To change just one default record use +\code{\link{add_default_record_to_session}} or +\code{\link{remove_default_record_from_session}}. +} +\examples{ +\donttest{ +\dontrun{ +# Rename a session +patch_session(vol_id = 1, session_id = 42, name = "Renamed session") + +# Update the date and precision +patch_session( + vol_id = 1, + session_id = 42, + date = list(year = 2024, month = 3, day = 15), + date_precision = "FULL" +) +} +} +} +\seealso{ +\code{\link{update_session}}, \code{\link{create_session}}, +\code{\link{add_default_record_to_session}}, +\code{\link{remove_default_record_from_session}} +} diff --git a/man/remove_default_record_from_session.Rd b/man/remove_default_record_from_session.Rd new file mode 100644 index 0000000..02340bd --- /dev/null +++ b/man/remove_default_record_from_session.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/remove_default_record_from_session.R +\name{remove_default_record_from_session} +\alias{remove_default_record_from_session} +\title{Remove a Default Record from a Session} +\usage{ +remove_default_record_from_session( + vol_id = 1, + session_id, + record_id, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{record_id}{Numeric record identifier. Must be a positive integer.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +\code{TRUE} if the record was successfully removed, \code{FALSE} +otherwise. +} +\description{ +Detach a record from a session's default records. The record +must currently be a default record on the session, otherwise the server +returns \code{404}. +} +\examples{ +\donttest{ +\dontrun{ +remove_default_record_from_session(vol_id = 1, session_id = 42, record_id = 101) +} +} +} +\seealso{ +\code{\link{add_default_record_to_session}} +} diff --git a/man/update_session.Rd b/man/update_session.Rd new file mode 100644 index 0000000..c11a032 --- /dev/null +++ b/man/update_session.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/update_session.R +\name{update_session} +\alias{update_session} +\title{Replace a Session in Databrary Volume (PUT)} +\usage{ +update_session( + vol_id = 1, + session_id, + name, + release_level = NULL, + source_date = NULL, + date = NULL, + date_precision = NULL, + default_records = NULL, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Target volume number. Must be a positive integer.} + +\item{session_id}{Numeric session identifier. Must be a positive integer.} + +\item{name}{New session name. Required, non-empty after trim.} + +\item{release_level}{Optional release level (e.g. \code{"PRIVATE"}, +\code{"SHARED"}, \code{"EXCERPTS"}, \code{"PUBLIC"}).} + +\item{source_date}{Optional session date. A length-1 \code{Date} object or +ISO \code{"YYYY-MM-DD"} string. Mutually exclusive with \code{date}.} + +\item{date}{Optional structured date list with named fields \code{year}, +\code{month}, \code{day}, and optional \code{is_estimated} (logical). +Mutually exclusive with \code{source_date}.} + +\item{date_precision}{Optional precision for \code{date}: e.g. +\code{"FULL"}, \code{"YEAR"}.} + +\item{default_records}{Optional integer vector of record IDs. The server +replaces the session's current default records with this set; omit to +leave them unchanged.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Defaults to \code{NULL}.} +} +\value{ +A list with the updated session's metadata (same shape as +\code{\link{get_session_by_id}}), or \code{NULL} if the update fails. +} +\description{ +Sends a PUT request to fully replace a session's writable +fields. \code{name} is required (non-empty); other fields are optional and +default to server-side values when omitted (the underlying serializer +marks them \code{required=FALSE}). Use \code{\link{patch_session}} for +partial updates when you don't want full-replacement semantics. +} +\examples{ +\donttest{ +\dontrun{ +# Rename a session via PUT +update_session(vol_id = 1, session_id = 42, name = "Replacement name") +} +} +} +\seealso{ +\code{\link{patch_session}}, \code{\link{create_session}} +} diff --git a/tests/testthat/test-add_default_record_to_session.R b/tests/testthat/test-add_default_record_to_session.R new file mode 100644 index 0000000..c74930a --- /dev/null +++ b/tests/testthat/test-add_default_record_to_session.R @@ -0,0 +1,180 @@ +# add_default_record_to_session() ---------------------------------------------- +login_test_account() + +# Sandbox volume 1777, category 6 ("task") is used elsewhere for record tests. +TEST_VOL <- 1777 +TEST_CATEGORY <- 6 + +new_session_id <- function(name = "add_default_record test") { + created <- create_session(vol_id = TEST_VOL, name = name, vb = FALSE) + if (is.null(created)) { + return(NULL) + } + created$id +} + +new_record_id <- function(name = "add_default_record test record") { + created <- create_volume_record( + vol_id = TEST_VOL, + category_id = TEST_CATEGORY, + name = name, + vb = FALSE + ) + if (is.null(created)) { + return(NULL) + } + created$record_id +} + +test_that("add_default_record_to_session attaches a record", { + sid <- new_session_id("add_default_record happy path") + skip_if_null_response(sid, "create_session for add_default_record happy path") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + rid <- new_record_id("add_default_record happy path record") + skip_if_null_response(rid, "create_volume_record for add_default_record happy path") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + result <- add_default_record_to_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = rid, + vb = FALSE + ) + expect_true(result) + + # Verify it shows up among the session's default_records + session <- get_session_by_id( + vol_id = TEST_VOL, + session_id = sid, + vb = FALSE + ) + default_ids <- vapply( + session$default_records, + function(r) as.integer(r$id), + integer(1) + ) + expect_true(as.integer(rid) %in% default_ids) +}) + +test_that("add_default_record_to_session returns FALSE for non-existent record", { + sid <- new_session_id("add_default_record non-existent record") + skip_if_null_response(sid, "create_session for non-existent record test") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + expect_false( + add_default_record_to_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = 999999999, + vb = FALSE + ) + ) +}) + +test_that("add_default_record_to_session returns FALSE for non-existent session", { + rid <- new_record_id("add_default_record non-existent session record") + skip_if_null_response(rid, "create_volume_record for non-existent session test") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + expect_false( + add_default_record_to_session( + vol_id = TEST_VOL, + session_id = 999999999, + record_id = rid, + vb = FALSE + ) + ) +}) + +test_that("add_default_record_to_session works with verbose mode", { + sid <- new_session_id("add_default_record vb") + skip_if_null_response(sid, "create_session for add_default_record vb") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + rid <- new_record_id("add_default_record vb record") + skip_if_null_response(rid, "create_volume_record for add_default_record vb") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + expect_true( + add_default_record_to_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = rid, + vb = TRUE + ) + ) +}) + +test_that("add_default_record_to_session works with custom request object", { + sid <- new_session_id("add_default_record custom rq") + skip_if_null_response(sid, "create_session for add_default_record custom rq") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + rid <- new_record_id("add_default_record custom rq record") + skip_if_null_response(rid, "create_volume_record for add_default_record custom rq") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + custom_rq <- databraryr::make_default_request() + expect_true( + add_default_record_to_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = rid, + rq = custom_rq, + vb = FALSE + ) + ) +}) + +test_that("add_default_record_to_session rejects invalid vol_id", { + expect_error(add_default_record_to_session(vol_id = -1, session_id = 1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 0, session_id = 1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = "1", session_id = 1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = TRUE, session_id = 1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = c(1, 2), session_id = 1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1.5, session_id = 1, record_id = 1)) +}) + +test_that("add_default_record_to_session rejects invalid session_id", { + expect_error(add_default_record_to_session(vol_id = 1, session_id = -1, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 0, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = "1", record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = TRUE, record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = c(1, 2), record_id = 1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1.5, record_id = 1)) +}) + +test_that("add_default_record_to_session rejects invalid record_id", { + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = -1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 0)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = "1")) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = TRUE)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = c(1, 2))) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1.5)) +}) + +test_that("add_default_record_to_session rejects invalid vb parameter", { + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, vb = -1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, vb = "a")) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, vb = c(TRUE, FALSE))) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, vb = NULL)) +}) + +test_that("add_default_record_to_session rejects invalid rq parameter", { + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, rq = "a")) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, rq = -1)) + expect_error(add_default_record_to_session(vol_id = 1, session_id = 1, record_id = 1, rq = TRUE)) +}) diff --git a/tests/testthat/test-check_duplicate_files_in_session.R b/tests/testthat/test-check_duplicate_files_in_session.R new file mode 100644 index 0000000..d817022 --- /dev/null +++ b/tests/testthat/test-check_duplicate_files_in_session.R @@ -0,0 +1,153 @@ +# check_duplicate_files_in_session() ------------------------------------------- +login_test_account() + +TEST_VOL <- 1777 + +new_session_id <- function(name = "check_duplicate_files test") { + created <- create_session(vol_id = TEST_VOL, name = name, vb = FALSE) + if (is.null(created)) { + return(NULL) + } + created$id +} + +test_that("check_duplicate_files_in_session returns a tibble for an empty session", { + sid <- new_session_id("check_duplicate_files happy path") + skip_if_null_response(sid, "create_session for check_duplicate_files happy path") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + filenames <- c("nonexistent_a.mp4", "nonexistent_b.mp4") + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = sid, + filenames = filenames, + vb = FALSE + ) + skip_if_null_response(result, "check_duplicate_files_in_session(empty session)") + + expect_s3_class(result, "tbl_df") + expect_named(result, c("filename", "exists")) + expect_equal(nrow(result), length(filenames)) + expect_equal(result$filename, filenames) + expect_type(result$exists, "logical") + expect_true(all(!result$exists)) +}) + +test_that("check_duplicate_files_in_session preserves input order", { + sid <- new_session_id("check_duplicate_files order") + skip_if_null_response(sid, "create_session for order test") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + filenames <- c("z.mp4", "a.mp4", "m.mp4") + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = sid, + filenames = filenames, + vb = FALSE + ) + skip_if_null_response(result, "check_duplicate_files_in_session(order)") + + expect_equal(result$filename, filenames) +}) + +test_that("check_duplicate_files_in_session works with a single filename", { + sid <- new_session_id("check_duplicate_files single") + skip_if_null_response(sid, "create_session for single filename test") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = sid, + filenames = "only.mp4", + vb = FALSE + ) + skip_if_null_response(result, "check_duplicate_files_in_session(single)") + + expect_equal(nrow(result), 1L) + expect_equal(result$filename, "only.mp4") + expect_false(result$exists) +}) + +test_that("check_duplicate_files_in_session returns NULL for non-existent session", { + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = 999999999, + filenames = c("a.mp4"), + vb = FALSE + ) + expect_null(result) +}) + +test_that("check_duplicate_files_in_session works with verbose mode", { + sid <- new_session_id("check_duplicate_files vb") + skip_if_null_response(sid, "create_session for check_duplicate_files vb") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = sid, + filenames = c("vb.mp4"), + vb = TRUE + ) + skip_if_null_response(result, "check_duplicate_files_in_session vb") + expect_s3_class(result, "tbl_df") +}) + +test_that("check_duplicate_files_in_session works with custom request object", { + sid <- new_session_id("check_duplicate_files custom rq") + skip_if_null_response(sid, "create_session for check_duplicate_files custom rq") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + custom_rq <- databraryr::make_default_request() + result <- check_duplicate_files_in_session( + vol_id = TEST_VOL, + session_id = sid, + filenames = c("custom.mp4"), + rq = custom_rq, + vb = FALSE + ) + skip_if_null_response(result, "check_duplicate_files_in_session custom rq") + expect_s3_class(result, "tbl_df") +}) + +test_that("check_duplicate_files_in_session rejects invalid filenames", { + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = character(0))) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = NULL)) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = 123)) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = list("a"))) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = TRUE)) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = c("a", NA))) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = c("a", ""))) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = c("a", " "))) +}) + +test_that("check_duplicate_files_in_session rejects invalid vol_id", { + expect_error(check_duplicate_files_in_session(vol_id = -1, session_id = 1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 0, session_id = 1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = "1", session_id = 1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = TRUE, session_id = 1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = c(1, 2), session_id = 1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1.5, session_id = 1, filenames = "a")) +}) + +test_that("check_duplicate_files_in_session rejects invalid session_id", { + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = -1, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 0, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = "1", filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = TRUE, filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = c(1, 2), filenames = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1.5, filenames = "a")) +}) + +test_that("check_duplicate_files_in_session rejects invalid vb parameter", { + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", vb = -1)) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", vb = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", vb = c(TRUE, FALSE))) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", vb = NULL)) +}) + +test_that("check_duplicate_files_in_session rejects invalid rq parameter", { + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", rq = "a")) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", rq = -1)) + expect_error(check_duplicate_files_in_session(vol_id = 1, session_id = 1, filenames = "a", rq = TRUE)) +}) diff --git a/tests/testthat/test-create_session.R b/tests/testthat/test-create_session.R new file mode 100644 index 0000000..0c5a014 --- /dev/null +++ b/tests/testthat/test-create_session.R @@ -0,0 +1,171 @@ +# create_session() ------------------------------------------------------------- +login_test_account() + +test_that("create_session creates a session with name only", { + result <- create_session(vol_id = 1777, name = "Test session", vb = FALSE) + skip_if_null_response(result, "create_session(vol_id = 1777, name = 'Test session')") + + if (!is.null(result$id)) { + delete_session(vol_id = 1777, session_id = result$id, vb = FALSE) + } + + expect_type(result, "list") + expect_true(!is.null(result$id)) + expect_true(is.numeric(result$id) || is.integer(result$id)) + expect_true(result$id > 0) + expect_equal(as.integer(result$volume), 1777L) + expect_equal(result$name, "Test session") +}) + +test_that("create_session accepts a Date source_date", { + result <- create_session( + vol_id = 1777, + name = "Test session with Date", + source_date = as.Date("2024-03-15"), + vb = FALSE + ) + skip_if_null_response(result, "create_session with Date source_date") + + if (!is.null(result$id)) { + delete_session(vol_id = 1777, session_id = result$id, vb = FALSE) + } + + expect_type(result, "list") + expect_true(!is.null(result$id)) +}) + +test_that("create_session accepts an ISO string source_date", { + result <- create_session( + vol_id = 1777, + name = "Test session with ISO date", + source_date = "2024-03-15", + vb = FALSE + ) + skip_if_null_response(result, "create_session with ISO source_date") + + if (!is.null(result$id)) { + delete_session(vol_id = 1777, session_id = result$id, vb = FALSE) + } + + expect_type(result, "list") +}) + +test_that("create_session works with verbose mode", { + result <- create_session( + vol_id = 1777, + name = "Test session vb", + vb = TRUE + ) + skip_if_null_response(result, "create_session with vb = TRUE") + + if (!is.null(result$id)) { + delete_session(vol_id = 1777, session_id = result$id, vb = FALSE) + } + + expect_type(result, "list") +}) + +test_that("create_session works with custom request object", { + custom_rq <- databraryr::make_default_request() + result <- create_session( + vol_id = 1777, + name = "Test session custom rq", + rq = custom_rq, + vb = FALSE + ) + skip_if_null_response(result, "create_session with custom_rq") + + if (!is.null(result$id)) { + delete_session(vol_id = 1777, session_id = result$id, vb = FALSE) + } + + expect_type(result, "list") +}) + +test_that("create_session returns NULL for non-existent volume", { + expect_null(create_session(vol_id = 999999999, name = "Test", vb = FALSE)) +}) + +test_that("create_session rejects invalid name", { + expect_error(create_session(vol_id = 1777, name = "")) + expect_error(create_session(vol_id = 1777, name = " ")) + expect_error(create_session(vol_id = 1777, name = 123)) + expect_error(create_session(vol_id = 1777, name = c("A", "B"))) + expect_error(create_session(vol_id = 1777, name = NULL)) + expect_error(create_session(vol_id = 1777, name = NA)) +}) + +test_that("create_session rejects providing both source_date and date", { + expect_error( + create_session( + vol_id = 1777, + name = "Test", + source_date = "2024-03-15", + date = list(year = 2024, month = 3, day = 15) + ) + ) +}) + +test_that("create_session rejects malformed source_date", { + expect_error(create_session(vol_id = 1777, name = "Test", source_date = "not-a-date")) + expect_error(create_session(vol_id = 1777, name = "Test", source_date = "")) + expect_error(create_session(vol_id = 1777, name = "Test", source_date = 123)) + expect_error( + create_session( + vol_id = 1777, + name = "Test", + source_date = as.Date(c("2024-01-01", "2024-02-01")) + ) + ) +}) + +test_that("create_session rejects malformed date list", { + expect_error(create_session(vol_id = 1777, name = "Test", date = "2024-03-15")) + expect_error(create_session(vol_id = 1777, name = "Test", date = list(2024, 3, 15))) +}) + +test_that("create_session rejects invalid release_level", { + expect_error(create_session(vol_id = 1777, name = "Test", release_level = "")) + expect_error(create_session(vol_id = 1777, name = "Test", release_level = 1)) + expect_error(create_session(vol_id = 1777, name = "Test", release_level = c("A", "B"))) +}) + +test_that("create_session rejects invalid date_precision", { + expect_error(create_session(vol_id = 1777, name = "Test", date_precision = "")) + expect_error(create_session(vol_id = 1777, name = "Test", date_precision = 1)) + expect_error(create_session(vol_id = 1777, name = "Test", date_precision = c("FULL", "YEAR"))) +}) + +test_that("create_session rejects invalid default_records", { + expect_error(create_session(vol_id = 1777, name = "Test", default_records = "1")) + expect_error(create_session(vol_id = 1777, name = "Test", default_records = c(1, -2))) + expect_error(create_session(vol_id = 1777, name = "Test", default_records = c(1, 0))) + expect_error(create_session(vol_id = 1777, name = "Test", default_records = c(1.5, 2))) + expect_error(create_session(vol_id = 1777, name = "Test", default_records = integer(0))) +}) + +test_that("create_session rejects invalid vol_id", { + expect_error(create_session(vol_id = -1, name = "Test")) + expect_error(create_session(vol_id = 0, name = "Test")) + expect_error(create_session(vol_id = "1", name = "Test")) + expect_error(create_session(vol_id = TRUE, name = "Test")) + expect_error(create_session(vol_id = list(a = 1), name = "Test")) + expect_error(create_session(vol_id = c(1, 2), name = "Test")) + expect_error(create_session(vol_id = 1.5, name = "Test")) +}) + +test_that("create_session rejects invalid vb parameter", { + expect_error(create_session(vol_id = 1777, name = "Test", vb = -1)) + expect_error(create_session(vol_id = 1777, name = "Test", vb = "a")) + expect_error(create_session(vol_id = 1777, name = "Test", vb = list(a = 1))) + expect_error(create_session(vol_id = 1777, name = "Test", vb = c(TRUE, FALSE))) + expect_error(create_session(vol_id = 1777, name = "Test", vb = NULL)) +}) + +test_that("create_session rejects invalid rq parameter", { + expect_error(create_session(vol_id = 1777, name = "Test", rq = "a")) + expect_error(create_session(vol_id = 1777, name = "Test", rq = -1)) + expect_error(create_session(vol_id = 1777, name = "Test", rq = c(2, 3))) + expect_error(create_session(vol_id = 1777, name = "Test", rq = list(a = 1))) + expect_error(create_session(vol_id = 1777, name = "Test", rq = TRUE)) +}) diff --git a/tests/testthat/test-delete_session.R b/tests/testthat/test-delete_session.R new file mode 100644 index 0000000..ab53d05 --- /dev/null +++ b/tests/testthat/test-delete_session.R @@ -0,0 +1,86 @@ +# delete_session() ------------------------------------------------------------- +login_test_account() + +test_that("delete_session deletes an existing session", { + created <- create_session(vol_id = 1777, name = "delete_session happy path", vb = FALSE) + skip_if_null_response(created, "create_session for delete_session happy path") + + session_id <- created$id + result <- delete_session(vol_id = 1777, session_id = session_id, vb = FALSE) + + expect_true(result) + + # Verify it's gone + expect_null(get_session_by_id(vol_id = 1777, session_id = session_id, vb = FALSE)) +}) + +test_that("delete_session returns FALSE for non-existent session", { + expect_false(delete_session(vol_id = 1777, session_id = 999999999, vb = FALSE)) +}) + +test_that("delete_session works with verbose mode", { + created <- create_session(vol_id = 1777, name = "delete_session vb", vb = FALSE) + skip_if_null_response(created, "create_session for delete_session vb") + + expect_true(delete_session(vol_id = 1777, session_id = created$id, vb = TRUE)) +}) + +test_that("delete_session works with custom request object", { + created <- create_session( + vol_id = 1777, + name = "delete_session custom rq", + vb = FALSE + ) + skip_if_null_response(created, "create_session for delete_session custom rq") + + custom_rq <- databraryr::make_default_request() + expect_true( + delete_session( + vol_id = 1777, + session_id = created$id, + rq = custom_rq, + vb = FALSE + ) + ) +}) + +test_that("delete_session rejects invalid vol_id", { + expect_error(delete_session(vol_id = -1, session_id = 1)) + expect_error(delete_session(vol_id = 0, session_id = 1)) + expect_error(delete_session(vol_id = "1", session_id = 1)) + expect_error(delete_session(vol_id = TRUE, session_id = 1)) + expect_error(delete_session(vol_id = list(a = 1), session_id = 1)) + expect_error(delete_session(vol_id = c(1, 2), session_id = 1)) + expect_error(delete_session(vol_id = 1.5, session_id = 1)) + expect_error(delete_session(vol_id = NULL, session_id = 1)) + expect_error(delete_session(vol_id = NA, session_id = 1)) +}) + +test_that("delete_session rejects invalid session_id", { + expect_error(delete_session(vol_id = 1777, session_id = -1)) + expect_error(delete_session(vol_id = 1777, session_id = 0)) + expect_error(delete_session(vol_id = 1777, session_id = "1")) + expect_error(delete_session(vol_id = 1777, session_id = TRUE)) + expect_error(delete_session(vol_id = 1777, session_id = list(a = 1))) + expect_error(delete_session(vol_id = 1777, session_id = c(1, 2))) + expect_error(delete_session(vol_id = 1777, session_id = 1.5)) + expect_error(delete_session(vol_id = 1777, session_id = NULL)) + expect_error(delete_session(vol_id = 1777, session_id = NA)) +}) + +test_that("delete_session rejects invalid vb parameter", { + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = -1)) + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = 3)) + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = "a")) + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = list(a = 1))) + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = c(TRUE, FALSE))) + expect_error(delete_session(vol_id = 1777, session_id = 1, vb = NULL)) +}) + +test_that("delete_session rejects invalid rq parameter", { + expect_error(delete_session(vol_id = 1777, session_id = 1, rq = "a")) + expect_error(delete_session(vol_id = 1777, session_id = 1, rq = -1)) + expect_error(delete_session(vol_id = 1777, session_id = 1, rq = c(2, 3))) + expect_error(delete_session(vol_id = 1777, session_id = 1, rq = list(a = 1))) + expect_error(delete_session(vol_id = 1777, session_id = 1, rq = TRUE)) +}) diff --git a/tests/testthat/test-patch_session.R b/tests/testthat/test-patch_session.R new file mode 100644 index 0000000..1930553 --- /dev/null +++ b/tests/testthat/test-patch_session.R @@ -0,0 +1,169 @@ +# patch_session() -------------------------------------------------------------- +login_test_account() + +# Roundtrip helper: create a fresh session for each test, return its id. +new_session_id <- function(name = "patch_session test") { + created <- create_session(vol_id = 1777, name = name, vb = FALSE) + if (is.null(created)) { + return(NULL) + } + created$id +} + +test_that("patch_session updates name", { + sid <- new_session_id("patch_session original") + skip_if_null_response(sid, "create_session for patch_session name test") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- patch_session( + vol_id = 1777, + session_id = sid, + name = "patch_session renamed", + vb = FALSE + ) + skip_if_null_response(result, "patch_session(name=...)") + + expect_type(result, "list") + expect_equal(result$name, "patch_session renamed") + expect_equal(as.integer(result$id), as.integer(sid)) +}) + +test_that("patch_session updates source_date with a Date object", { + sid <- new_session_id("patch_session source_date Date") + skip_if_null_response(sid, "create_session for patch_session source_date Date") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- patch_session( + vol_id = 1777, + session_id = sid, + source_date = as.Date("2024-03-15"), + vb = FALSE + ) + skip_if_null_response(result, "patch_session(source_date=Date)") + expect_type(result, "list") +}) + +test_that("patch_session returns NULL when no fields provided", { + expect_null(patch_session(vol_id = 1777, session_id = 1, vb = FALSE)) +}) + +test_that("patch_session returns NULL for non-existent session", { + result <- patch_session( + vol_id = 1777, + session_id = 999999999, + name = "nope", + vb = FALSE + ) + expect_null(result) +}) + +test_that("patch_session works with verbose mode", { + sid <- new_session_id("patch_session vb") + skip_if_null_response(sid, "create_session for patch_session vb") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- patch_session( + vol_id = 1777, + session_id = sid, + name = "patch_session vb renamed", + vb = TRUE + ) + skip_if_null_response(result, "patch_session vb") + expect_type(result, "list") +}) + +test_that("patch_session works with custom request object", { + sid <- new_session_id("patch_session custom rq") + skip_if_null_response(sid, "create_session for patch_session custom rq") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + custom_rq <- databraryr::make_default_request() + result <- patch_session( + vol_id = 1777, + session_id = sid, + name = "patch_session custom rq renamed", + rq = custom_rq, + vb = FALSE + ) + skip_if_null_response(result, "patch_session custom rq") + expect_type(result, "list") +}) + +test_that("patch_session rejects invalid name", { + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "")) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = " ")) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = 123)) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = c("A", "B"))) +}) + +test_that("patch_session rejects providing both source_date and date", { + expect_error( + patch_session( + vol_id = 1777, + session_id = 1, + source_date = "2024-03-15", + date = list(year = 2024, month = 3, day = 15) + ) + ) +}) + +test_that("patch_session rejects malformed source_date", { + expect_error(patch_session(vol_id = 1777, session_id = 1, source_date = "not-a-date")) + expect_error(patch_session(vol_id = 1777, session_id = 1, source_date = "")) + expect_error(patch_session(vol_id = 1777, session_id = 1, source_date = 123)) +}) + +test_that("patch_session rejects malformed date", { + expect_error(patch_session(vol_id = 1777, session_id = 1, date = "2024-03-15")) + expect_error(patch_session(vol_id = 1777, session_id = 1, date = list(2024, 3, 15))) +}) + +test_that("patch_session rejects invalid release_level", { + expect_error(patch_session(vol_id = 1777, session_id = 1, release_level = "")) + expect_error(patch_session(vol_id = 1777, session_id = 1, release_level = 1)) + expect_error(patch_session(vol_id = 1777, session_id = 1, release_level = c("A", "B"))) +}) + +test_that("patch_session rejects invalid date_precision", { + expect_error(patch_session(vol_id = 1777, session_id = 1, date_precision = "")) + expect_error(patch_session(vol_id = 1777, session_id = 1, date_precision = 1)) +}) + +test_that("patch_session rejects invalid default_records", { + expect_error(patch_session(vol_id = 1777, session_id = 1, default_records = "1")) + expect_error(patch_session(vol_id = 1777, session_id = 1, default_records = c(1, -2))) + expect_error(patch_session(vol_id = 1777, session_id = 1, default_records = c(1, 0))) + expect_error(patch_session(vol_id = 1777, session_id = 1, default_records = c(1.5, 2))) + expect_error(patch_session(vol_id = 1777, session_id = 1, default_records = integer(0))) +}) + +test_that("patch_session rejects invalid vol_id", { + expect_error(patch_session(vol_id = -1, session_id = 1, name = "x")) + expect_error(patch_session(vol_id = 0, session_id = 1, name = "x")) + expect_error(patch_session(vol_id = "1", session_id = 1, name = "x")) + expect_error(patch_session(vol_id = TRUE, session_id = 1, name = "x")) + expect_error(patch_session(vol_id = c(1, 2), session_id = 1, name = "x")) + expect_error(patch_session(vol_id = 1.5, session_id = 1, name = "x")) +}) + +test_that("patch_session rejects invalid session_id", { + expect_error(patch_session(vol_id = 1777, session_id = -1, name = "x")) + expect_error(patch_session(vol_id = 1777, session_id = 0, name = "x")) + expect_error(patch_session(vol_id = 1777, session_id = "1", name = "x")) + expect_error(patch_session(vol_id = 1777, session_id = TRUE, name = "x")) + expect_error(patch_session(vol_id = 1777, session_id = c(1, 2), name = "x")) + expect_error(patch_session(vol_id = 1777, session_id = 1.5, name = "x")) +}) + +test_that("patch_session rejects invalid vb parameter", { + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", vb = -1)) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", vb = "a")) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", vb = c(TRUE, FALSE))) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", vb = NULL)) +}) + +test_that("patch_session rejects invalid rq parameter", { + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", rq = "a")) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", rq = -1)) + expect_error(patch_session(vol_id = 1777, session_id = 1, name = "x", rq = TRUE)) +}) diff --git a/tests/testthat/test-remove_default_record_from_session.R b/tests/testthat/test-remove_default_record_from_session.R new file mode 100644 index 0000000..46db8e1 --- /dev/null +++ b/tests/testthat/test-remove_default_record_from_session.R @@ -0,0 +1,221 @@ +# remove_default_record_from_session() ----------------------------------------- +login_test_account() + +TEST_VOL <- 1777 +TEST_CATEGORY <- 6 + +new_session_id <- function(name = "remove_default_record test") { + created <- create_session(vol_id = TEST_VOL, name = name, vb = FALSE) + if (is.null(created)) { + return(NULL) + } + created$id +} + +new_record_id <- function(name = "remove_default_record test record") { + created <- create_volume_record( + vol_id = TEST_VOL, + category_id = TEST_CATEGORY, + name = name, + vb = FALSE + ) + if (is.null(created)) { + return(NULL) + } + created$record_id +} + +# Setup helper: create session + record + attach the record as a default. +setup_attached <- function(name) { + sid <- new_session_id(paste0(name, " session")) + if (is.null(sid)) return(NULL) + rid <- new_record_id(paste0(name, " record")) + if (is.null(rid)) { + delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE) + return(NULL) + } + attached <- add_default_record_to_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = rid, + vb = FALSE + ) + if (!isTRUE(attached)) { + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE) + delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE) + return(NULL) + } + list(session_id = sid, record_id = rid) +} + +test_that("remove_default_record_from_session detaches a record", { + setup <- setup_attached("remove_default_record happy path") + skip_if_null_response(setup, "setup for remove_default_record happy path") + on.exit( + { + delete_volume_record(vol_id = TEST_VOL, record_id = setup$record_id, vb = FALSE) + delete_session(vol_id = TEST_VOL, session_id = setup$session_id, vb = FALSE) + }, + add = TRUE + ) + + result <- remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = setup$session_id, + record_id = setup$record_id, + vb = FALSE + ) + expect_true(result) + + # Verify the record is no longer among default_records + session <- get_session_by_id( + vol_id = TEST_VOL, + session_id = setup$session_id, + vb = FALSE + ) + default_ids <- vapply( + session$default_records, + function(r) as.integer(r$id), + integer(1) + ) + expect_false(as.integer(setup$record_id) %in% default_ids) +}) + +test_that("remove_default_record_from_session returns FALSE for unattached record", { + sid <- new_session_id("remove_default_record unattached") + skip_if_null_response(sid, "create_session for unattached test") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + rid <- new_record_id("remove_default_record unattached record") + skip_if_null_response(rid, "create_volume_record for unattached test") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + expect_false( + remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = rid, + vb = FALSE + ) + ) +}) + +test_that("remove_default_record_from_session returns FALSE for non-existent record", { + sid <- new_session_id("remove_default_record non-existent record") + skip_if_null_response(sid, "create_session for non-existent record test") + on.exit(delete_session(vol_id = TEST_VOL, session_id = sid, vb = FALSE), add = TRUE) + + expect_false( + remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = sid, + record_id = 999999999, + vb = FALSE + ) + ) +}) + +test_that("remove_default_record_from_session returns FALSE for non-existent session", { + rid <- new_record_id("remove_default_record non-existent session record") + skip_if_null_response(rid, "create_volume_record for non-existent session test") + on.exit( + delete_volume_record(vol_id = TEST_VOL, record_id = rid, vb = FALSE), + add = TRUE + ) + + expect_false( + remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = 999999999, + record_id = rid, + vb = FALSE + ) + ) +}) + +test_that("remove_default_record_from_session works with verbose mode", { + setup <- setup_attached("remove_default_record vb") + skip_if_null_response(setup, "setup for remove_default_record vb") + on.exit( + { + delete_volume_record(vol_id = TEST_VOL, record_id = setup$record_id, vb = FALSE) + delete_session(vol_id = TEST_VOL, session_id = setup$session_id, vb = FALSE) + }, + add = TRUE + ) + + expect_true( + remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = setup$session_id, + record_id = setup$record_id, + vb = TRUE + ) + ) +}) + +test_that("remove_default_record_from_session works with custom request object", { + setup <- setup_attached("remove_default_record custom rq") + skip_if_null_response(setup, "setup for remove_default_record custom rq") + on.exit( + { + delete_volume_record(vol_id = TEST_VOL, record_id = setup$record_id, vb = FALSE) + delete_session(vol_id = TEST_VOL, session_id = setup$session_id, vb = FALSE) + }, + add = TRUE + ) + + custom_rq <- databraryr::make_default_request() + expect_true( + remove_default_record_from_session( + vol_id = TEST_VOL, + session_id = setup$session_id, + record_id = setup$record_id, + rq = custom_rq, + vb = FALSE + ) + ) +}) + +test_that("remove_default_record_from_session rejects invalid vol_id", { + expect_error(remove_default_record_from_session(vol_id = -1, session_id = 1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 0, session_id = 1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = "1", session_id = 1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = TRUE, session_id = 1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = c(1, 2), session_id = 1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1.5, session_id = 1, record_id = 1)) +}) + +test_that("remove_default_record_from_session rejects invalid session_id", { + expect_error(remove_default_record_from_session(vol_id = 1, session_id = -1, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 0, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = "1", record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = TRUE, record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = c(1, 2), record_id = 1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1.5, record_id = 1)) +}) + +test_that("remove_default_record_from_session rejects invalid record_id", { + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = -1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 0)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = "1")) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = TRUE)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = c(1, 2))) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1.5)) +}) + +test_that("remove_default_record_from_session rejects invalid vb parameter", { + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, vb = -1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, vb = "a")) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, vb = c(TRUE, FALSE))) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, vb = NULL)) +}) + +test_that("remove_default_record_from_session rejects invalid rq parameter", { + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, rq = "a")) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, rq = -1)) + expect_error(remove_default_record_from_session(vol_id = 1, session_id = 1, record_id = 1, rq = TRUE)) +}) diff --git a/tests/testthat/test-update_session.R b/tests/testthat/test-update_session.R new file mode 100644 index 0000000..7b25593 --- /dev/null +++ b/tests/testthat/test-update_session.R @@ -0,0 +1,168 @@ +# update_session() ------------------------------------------------------------- +login_test_account() + +new_session_id <- function(name = "update_session test") { + created <- create_session(vol_id = 1777, name = name, vb = FALSE) + if (is.null(created)) { + return(NULL) + } + created$id +} + +test_that("update_session replaces name via PUT", { + sid <- new_session_id("update_session original") + skip_if_null_response(sid, "create_session for update_session name test") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- update_session( + vol_id = 1777, + session_id = sid, + name = "update_session replaced", + vb = FALSE + ) + skip_if_null_response(result, "update_session(name=...)") + + expect_type(result, "list") + expect_equal(result$name, "update_session replaced") + expect_equal(as.integer(result$id), as.integer(sid)) +}) + +test_that("update_session replaces source_date with a Date object", { + sid <- new_session_id("update_session source_date Date") + skip_if_null_response(sid, "create_session for update_session source_date Date") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- update_session( + vol_id = 1777, + session_id = sid, + name = "update_session source_date Date", + source_date = as.Date("2024-03-15"), + vb = FALSE + ) + skip_if_null_response(result, "update_session(source_date=Date)") + expect_type(result, "list") +}) + +test_that("update_session returns NULL for non-existent session", { + result <- update_session( + vol_id = 1777, + session_id = 999999999, + name = "nope", + vb = FALSE + ) + expect_null(result) +}) + +test_that("update_session works with verbose mode", { + sid <- new_session_id("update_session vb") + skip_if_null_response(sid, "create_session for update_session vb") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + result <- update_session( + vol_id = 1777, + session_id = sid, + name = "update_session vb replaced", + vb = TRUE + ) + skip_if_null_response(result, "update_session vb") + expect_type(result, "list") +}) + +test_that("update_session works with custom request object", { + sid <- new_session_id("update_session custom rq") + skip_if_null_response(sid, "create_session for update_session custom rq") + on.exit(delete_session(vol_id = 1777, session_id = sid, vb = FALSE), add = TRUE) + + custom_rq <- databraryr::make_default_request() + result <- update_session( + vol_id = 1777, + session_id = sid, + name = "update_session custom rq replaced", + rq = custom_rq, + vb = FALSE + ) + skip_if_null_response(result, "update_session custom rq") + expect_type(result, "list") +}) + +test_that("update_session rejects missing/invalid name", { + expect_error(update_session(vol_id = 1777, session_id = 1)) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = " ")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = 123)) + expect_error(update_session(vol_id = 1777, session_id = 1, name = c("A", "B"))) + expect_error(update_session(vol_id = 1777, session_id = 1, name = NULL)) + expect_error(update_session(vol_id = 1777, session_id = 1, name = NA)) +}) + +test_that("update_session rejects providing both source_date and date", { + expect_error( + update_session( + vol_id = 1777, + session_id = 1, + name = "x", + source_date = "2024-03-15", + date = list(year = 2024, month = 3, day = 15) + ) + ) +}) + +test_that("update_session rejects malformed source_date", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", source_date = "not-a-date")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", source_date = "")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", source_date = 123)) +}) + +test_that("update_session rejects malformed date", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", date = "2024-03-15")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", date = list(2024, 3, 15))) +}) + +test_that("update_session rejects invalid release_level", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", release_level = "")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", release_level = 1)) +}) + +test_that("update_session rejects invalid date_precision", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", date_precision = "")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", date_precision = 1)) +}) + +test_that("update_session rejects invalid default_records", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", default_records = "1")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", default_records = c(1, -2))) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", default_records = c(1, 0))) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", default_records = c(1.5, 2))) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", default_records = integer(0))) +}) + +test_that("update_session rejects invalid vol_id", { + expect_error(update_session(vol_id = -1, session_id = 1, name = "x")) + expect_error(update_session(vol_id = 0, session_id = 1, name = "x")) + expect_error(update_session(vol_id = "1", session_id = 1, name = "x")) + expect_error(update_session(vol_id = TRUE, session_id = 1, name = "x")) + expect_error(update_session(vol_id = c(1, 2), session_id = 1, name = "x")) + expect_error(update_session(vol_id = 1.5, session_id = 1, name = "x")) +}) + +test_that("update_session rejects invalid session_id", { + expect_error(update_session(vol_id = 1777, session_id = -1, name = "x")) + expect_error(update_session(vol_id = 1777, session_id = 0, name = "x")) + expect_error(update_session(vol_id = 1777, session_id = "1", name = "x")) + expect_error(update_session(vol_id = 1777, session_id = TRUE, name = "x")) + expect_error(update_session(vol_id = 1777, session_id = c(1, 2), name = "x")) + expect_error(update_session(vol_id = 1777, session_id = 1.5, name = "x")) +}) + +test_that("update_session rejects invalid vb parameter", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", vb = -1)) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", vb = "a")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", vb = c(TRUE, FALSE))) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", vb = NULL)) +}) + +test_that("update_session rejects invalid rq parameter", { + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", rq = "a")) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", rq = -1)) + expect_error(update_session(vol_id = 1777, session_id = 1, name = "x", rq = TRUE)) +})