diff --git a/DESCRIPTION b/DESCRIPTION index 963846e3b..317519e26 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,7 +32,8 @@ Imports: magrittr, mime, lifecycle (>= 0.2.0), - ellipsis (>= 0.3.0) + ellipsis (>= 0.3.0), + rlang LazyData: TRUE ByteCompile: TRUE Suggests: @@ -46,7 +47,8 @@ Suggests: yaml, feather, future, - rstudioapi + rstudioapi, + mockery (>= 0.4.2) RoxygenNote: 7.1.1 Collate: 'async.R' diff --git a/NAMESPACE b/NAMESPACE index fac11426b..fa6fd8477 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -104,6 +104,8 @@ importFrom(jsonlite,parse_json) importFrom(jsonlite,toJSON) importFrom(lifecycle,deprecated) importFrom(magrittr,"%>%") +importFrom(rlang,"%||%") +importFrom(rlang,missing_arg) importFrom(stats,runif) importFrom(stats,setNames) importFrom(utils,file.edit) diff --git a/NEWS.md b/NEWS.md index 98bd59e1c..cdb077463 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,12 @@ plumber 1.0.0.9999 Development version * OpenAPI Specification can be set using a file path. (@meztez #696) +* Un-deprecated `Plumber$run(debug=, swaggerCallback=)` and added the parameters for `Plumber$run(docs=, quiet=)` and `pr_run(debug=, docs=, swaggerCallback=, quiet=)`. Now, all four parameters will not produce lingering effects on the `Plumber` router. (@jcheng5 #765) + * Setting `quiet = TRUE` will suppress routine startup messages. + * Setting `debug = TRUE`, will display information when an error occurs. See `pr_set_debug()`. + * Setting `docs` will update the visual documentation. See `pr_set_docs()`. + * Set `swaggerCallback` to a function which will be called with a url to the documentation, or `NULL` to do nothing. See `pr_set_docs_callback()`. + * To update a `PlumberEndpoint` path after initialization, call the new `PlumberEndpoint$setPath(path)`. This will update internal path matching meta data. (Active bindings were not used to avoid breaking changes.) (@blairj09 #770) ### Bug fixes diff --git a/R/plumber.R b/R/plumber.R index 056a07512..fe9aab86c 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1,5 +1,6 @@ #' @import R6 #' @import stringi +#' @importFrom rlang %||% NULL # used to identify annotation flags. @@ -86,7 +87,6 @@ Plumber <- R6Class( } # Initialize - private$maxSize <- getOption('plumber.maxRequestSize', 0) #0 Unlimited self$setSerializer(serializer_json()) # Default parsers to maintain legacy features self$setParsers(c("json", "form", "text", "octet", "multi")) @@ -94,8 +94,8 @@ Plumber <- R6Class( self$set404Handler(default404Handler) self$setDocs(TRUE) private$docs_info$has_not_been_set <- TRUE # set to know if `$setDocs()` has been called before `$run()` - self$setDocsCallback(getOption('plumber.docs.callback', getOption('plumber.swagger.url'))) - self$setDebug(interactive()) + private$docs_callback <- rlang::missing_arg() + private$debug <- NULL self$setApiSpec(NULL) # Add in the initial filters @@ -143,55 +143,111 @@ Plumber <- R6Class( #' Mac OS X, port numbers smaller than 1025 require root privileges. #' #' This value does not need to be explicitly assigned. To explicitly set it, see [options_plumber()]. - #' @param debug Deprecated. See `$setDebug()` - #' @param swagger Deprecated. See `$setDocs(docs)` or `$setApiSpec()` - #' @param swaggerCallback Deprecated. See `$setDocsCallback()` + #' @param debug If `TRUE`, it will provide more insight into your API errors. Using this value will only last for the duration of the run. If a `$setDebug()` has not been called, `debug` will default to `interactive()` at `$run()` time. See `$setDebug()` for more details. + #' @param swagger Deprecated. Please use `docs` instead. See `$setDocs(docs)` or `$setApiSpec()` for more customization. + #' @param swaggerCallback An optional single-argument function that is + #' called back with the URL to an OpenAPI user interface when one becomes + #' ready. If missing, defaults to information previously set with `$setDocsCallback()`. + #' This value will only be used while running the router. + #' @param docs Visual documentation value to use while running the API. + #' This value will only be used while running the router. + #' If missing, defaults to information previously set with `setDocs()`. + #' For more customization, see `$setDocs()` or [pr_set_docs()] for examples. + #' @param quiet If `TRUE`, don't print routine startup messages. + #' @param ... Should be empty. #' @importFrom lifecycle deprecated + #' @importFrom rlang missing_arg run = function( host = '127.0.0.1', port = getOption('plumber.port', NULL), swagger = deprecated(), - debug = deprecated(), - swaggerCallback = deprecated() + debug = missing_arg(), + swaggerCallback = missing_arg(), + ..., + # any new args should go below `...` + docs = missing_arg(), + quiet = FALSE ) { if (isTRUE(private$disable_run)) { stop("Plumber router `$run()` method should not be called while `plumb()`ing a file") } + ellipsis::check_dots_empty() + # Legacy support for RStudio pro products. # Checks must be kept for >= 2 yrs after plumber v1.0.0 release date - if (lifecycle::is_present(debug)) { - lifecycle::deprecate_warn("1.0.0", "run(debug = )", "setDebug(debug = )") - self$setDebug(debug) - } if (lifecycle::is_present(swagger)) { - if (is.function(swagger)) { - # between v0.4.6 and v1.0.0 - lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "setApiSpec(api = )") - self$setApiSpec(swagger) - # spec is now enabled by default. Do not alter + if (!rlang::is_missing(docs)) { + lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )", details = "`docs` will take preference (ignoring `swagger`)") + # (`docs` is resolved after `swagger` checks) } else { - if (isTRUE(private$docs_info$has_not_been_set)) { - # <= v0.4.6 - lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "setDocs(docs = )") - self$setDocs(swagger) + if (is.function(swagger)) { + # between v0.4.6 and v1.0.0 + lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$setApiSpec(api = )") + # set the new api function and force turn on the docs + old_api_spec_handler <- private$api_spec_handler + self$setApiSpec(swagger) + on.exit({ + private$api_spec_handler <- old_api_spec_handler + }, add = TRUE) + docs <- TRUE } else { - # $setDocs() has been called (other than during initialization). - # Believe that it is the correct behavior - # Warn about updating the run method - lifecycle::deprecate_warn("1.0.0", "run(swagger = )", details = "The Plumber docs have already been set. Ignoring `swagger` parameter.") + if (isTRUE(private$docs_info$has_not_been_set)) { + # <= v0.4.6 + lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )") + docs <- swagger + } else { + # $setDocs() has been called (other than during initialization). + # `docs` is not provided + # Believe that prior `$setDocs()` behavior is the correct behavior + # Warn about updating the run method + lifecycle::deprecate_warn("1.0.0", "Plumber$run(swagger = )", "Plumber$run(docs = )", details = "The Plumber docs have already been set. Ignoring `swagger` parameter.") + } } } } - if (lifecycle::is_present(swaggerCallback)) { - lifecycle::deprecate_warn("1.0.0", "run(swaggerCallback = )", "setDocsCallback(callback = )") - self$setDocsCallback(swaggerCallback) - } port <- findPort(port) - message("Running plumber API at ", urlHost(host = host, port = port, changeHostLocation = FALSE)) + # Delay setting max size option. It could be set in `plumber.R`, which is after initialization + private$maxSize <- getOption('plumber.maxRequestSize', 0) #0 Unlimited + + # Delay the setting of swaggerCallback as long as possible. + # An option could be set in `plumber.R`, which is after initialization + # Order: Run method parameter, internally set value, option, fallback option, NULL + swaggerCallback <- + rlang::maybe_missing(swaggerCallback, + rlang::maybe_missing(private$docs_callback, + getOption('plumber.docs.callback', getOption('plumber.swagger.url', NULL)) + ) + ) + + # Delay the setting of debug as long as possible. + # The router could be made in an interactive setting and used in background process. + # Do not determine if interactive until run time + prev_debug <- private$debug + on.exit({ + private$debug <- prev_debug + }, add = TRUE) + # Fix the debug value while running. + self$setDebug( + # Order: Run method param, internally set value, is interactive() + # `$getDebug()` is dynamic given `setDebug()` has never been called. + rlang::maybe_missing(debug, self$getDebug()) + ) + + docs_info <- + if (!rlang::is_missing(docs)) { + # Manually provided. Need to upgrade the parameter + upgrade_docs_parameter(docs) + } else { + private$docs_info + } + + if (!isTRUE(quiet)) { + message("Running plumber API at ", urlHost(host = host, port = port, changeHostLocation = FALSE)) + } # Set and restore the wd to make it appear that the proc is running local to the file's definition. if (!is.null(private$filename)) { @@ -199,15 +255,16 @@ Plumber <- R6Class( on.exit({setwd(old_wd)}, add = TRUE) } - if (isTRUE(private$docs_info$enabled)) { + if (isTRUE(docs_info$enabled)) { mount_docs( pr = self, host = host, port = port, - docs_info = private$docs_info, - callback = private$docs_callback + docs_info = docs_info, + callback = swaggerCallback, + quiet = quiet ) - on.exit(unmount_docs(self, private$docs_info), add = TRUE) + on.exit(unmount_docs(self, docs_info), add = TRUE) } on.exit(private$runHooks("exit"), add = TRUE) @@ -879,23 +936,7 @@ Plumber <- R6Class( docs = getOption("plumber.docs", TRUE), ... ) { - stopifnot(length(docs) == 1) - stopifnot(is.logical(docs) || is.character(docs)) - if (isTRUE(docs)) { - docs <- "swagger" - } - if (is.character(docs) && is_docs_available(docs)) { - enabled <- TRUE - } else { - enabled <- FALSE - docs <- "__not_enabled__" - } - private$docs_info <- list( - enabled = enabled, - docs = docs, - args = list(...), - has_not_been_set = FALSE - ) + private$docs_info <- upgrade_docs_parameter(docs, ...) }, #' @description Set a callback to notify where the API's visual documentation is located. #' @@ -918,7 +959,7 @@ Plumber <- R6Class( } private$docs_callback <- callback }, - #' @description Set debug value to include error messages + #' @description Set debug value to include error messages. #' #' See also: `$getDebug()` and [pr_set_debug()] #' @param debug `TRUE` provides more insight into your API errors. @@ -926,11 +967,11 @@ Plumber <- R6Class( stopifnot(length(debug) == 1) private$debug <- isTRUE(debug) }, - #' @description Retrieve the `debug` value. + #' @description Retrieve the `debug` value. If it has never been set, the result of `interactive()` will be used. #' #' See also: `$getDebug()` and [pr_set_debug()] getDebug = function() { - private$debug + private$debug %||% default_debug() }, #' @description Add a filter to plumber router #' @@ -1176,7 +1217,7 @@ Plumber <- R6Class( errorHandler = NULL, notFoundHandler = NULL, - maxSize = NULL, # Max request size in bytes + maxSize = 0, # Max request size in bytes. (0 is a no-op) api_spec_handler = NULL, docs_info = NULL, @@ -1271,9 +1312,32 @@ Plumber <- R6Class( +upgrade_docs_parameter <- function(docs, ...) { + stopifnot(length(docs) == 1) + stopifnot(is.logical(docs) || is.character(docs)) + if (isTRUE(docs)) { + docs <- "swagger" + } + if (is.character(docs) && is_docs_available(docs)) { + enabled <- TRUE + } else { + enabled <- FALSE + docs <- "__not_enabled__" + } + + list( + enabled = enabled, + docs = docs, + args = list(...), + has_not_been_set = FALSE + ) +} +default_debug <- function() { + interactive() +} urlHost <- function(scheme = "http", host, port, path = "", changeHostLocation = FALSE) { diff --git a/R/pr.R b/R/pr.R index ec2c4219e..6843bfb8a 100644 --- a/R/pr.R +++ b/R/pr.R @@ -475,6 +475,19 @@ pr_filter <- function(pr, #' @param port A number or integer that indicates the server port that should #' be listened on. Note that on most Unix-like systems including Linux and #' Mac OS X, port numbers smaller than 1025 require root privileges. +#' @param ... Should be empty. +#' @param debug If `TRUE`, it will provide more insight into your API errors. +#' Using this value will only last for the duration of the run. +#' If [pr_set_debug()] has not been called, `debug` will default to `interactive()` at [pr_run()] time +#' @param docs Visual documentation value to use while running the API. +#' This value will only be used while running the router. +#' If missing, defaults to information previously set with [pr_set_docs()]. +#' For more customization, see [pr_set_docs()] for examples. +#' @param swaggerCallback An optional single-argument function that is called +#' back with the URL to an OpenAPI user interface when one becomes ready. If +#' missing, defaults to information set with [pr_set_docs_callback()]. +#' This value will only be used while running the router. +#' @param quiet If `TRUE`, don't print routine startup messages. #' #' @examples #' \dontrun{ @@ -482,17 +495,34 @@ pr_filter <- function(pr, #' pr_run() #' #' pr() %>% -#' pr_run(port = 5762) +#' pr_run( +#' # manually set port +#' port = 5762, +#' # turn off visual documentation +#' docs = FALSE, +#' # do not display startup messages +#' quiet = TRUE +#' ) #' } #' #' @export pr_run <- function(pr, host = '127.0.0.1', - port = getOption('plumber.port', NULL) + port = getOption('plumber.port', NULL), + ..., + debug = missing_arg(), + docs = missing_arg(), + swaggerCallback = missing_arg(), + quiet = FALSE ) { validate_pr(pr) + ellipsis::check_dots_empty() pr$run(host = host, - port = port) + port = port, + debug = debug, + docs = docs, + swaggerCallback = swaggerCallback, + quiet = quiet) } diff --git a/R/ui.R b/R/ui.R index ffe5f1e32..b4c779d6b 100644 --- a/R/ui.R +++ b/R/ui.R @@ -2,7 +2,7 @@ # Mount OpenAPI and Docs #' @noRd -mount_docs <- function(pr, host, port, docs_info, callback) { +mount_docs <- function(pr, host, port, docs_info, callback, quiet = FALSE) { # return early if not enabled if (!isTRUE(docs_info$enabled)) { @@ -26,14 +26,18 @@ mount_docs <- function(pr, host, port, docs_info, callback) { # Mount Docs if (length(registered_docs()) == 0) { - message("No visual documentation options registered. See help(register_docs).") + if (!isTRUE(quiet)) { + message("No visual documentation options registered. See help(register_docs).") + } return() } if (is_docs_available(docs_info$docs)) { docs_mount <- .globals$docs[[docs_info$docs]]$mount docs_url <- do.call(docs_mount, c(list(pr, api_url), docs_info$args)) - message("Running ", docs_info$docs, " Docs at ", docs_url, sep = "") + if (!isTRUE(quiet)) { + message("Running ", docs_info$docs, " Docs at ", docs_url, sep = "") + } } else { return() } diff --git a/man/Plumber.Rd b/man/Plumber.Rd index 11736bc4e..f1a1bf705 100644 --- a/man/Plumber.Rd +++ b/man/Plumber.Rd @@ -219,8 +219,11 @@ See also: \code{\link[=pr_run]{pr_run()}} host = "127.0.0.1", port = getOption("plumber.port", NULL), swagger = deprecated(), - debug = deprecated(), - swaggerCallback = deprecated() + debug = missing_arg(), + swaggerCallback = missing_arg(), + ..., + docs = missing_arg(), + quiet = FALSE )}\if{html}{\out{}} } @@ -237,11 +240,23 @@ Mac OS X, port numbers smaller than 1025 require root privileges. This value does not need to be explicitly assigned. To explicitly set it, see \code{\link[=options_plumber]{options_plumber()}}.} -\item{\code{swagger}}{Deprecated. See \verb{$setDocs(docs)} or \verb{$setApiSpec()}} +\item{\code{swagger}}{Deprecated. Please use \code{docs} instead. See \verb{$setDocs(docs)} or \verb{$setApiSpec()} for more customization.} -\item{\code{debug}}{Deprecated. See \verb{$setDebug()}} +\item{\code{debug}}{If \code{TRUE}, it will provide more insight into your API errors. Using this value will only last for the duration of the run. If a \verb{$setDebug()} has not been called, \code{debug} will default to \code{interactive()} at \verb{$run()} time. See \verb{$setDebug()} for more details.} -\item{\code{swaggerCallback}}{Deprecated. See \verb{$setDocsCallback()}} +\item{\code{swaggerCallback}}{An optional single-argument function that is +called back with the URL to an OpenAPI user interface when one becomes +ready. If missing, defaults to information previously set with \verb{$setDocsCallback()}. +This value will only be used while running the router.} + +\item{\code{...}}{Should be empty.} + +\item{\code{docs}}{Visual documentation value to use while running the API. +This value will only be used while running the router. +If missing, defaults to information previously set with \code{setDocs()}. +For more customization, see \verb{$setDocs()} or \code{\link[=pr_set_docs]{pr_set_docs()}} for examples.} + +\item{\code{quiet}}{If \code{TRUE}, don't print routine startup messages.} } \if{html}{\out{}} } @@ -765,7 +780,7 @@ See also: \code{\link[=pr_set_docs_callback]{pr_set_docs_callback()}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-setDebug}{}}} \subsection{Method \code{setDebug()}}{ -Set debug value to include error messages +Set debug value to include error messages. See also: \verb{$getDebug()} and \code{\link[=pr_set_debug]{pr_set_debug()}} \subsection{Usage}{ @@ -784,7 +799,7 @@ See also: \verb{$getDebug()} and \code{\link[=pr_set_debug]{pr_set_debug()}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-getDebug}{}}} \subsection{Method \code{getDebug()}}{ -Retrieve the \code{debug} value. +Retrieve the \code{debug} value. If it has never been set, the result of \code{interactive()} will be used. See also: \verb{$getDebug()} and \code{\link[=pr_set_debug]{pr_set_debug()}} \subsection{Usage}{ diff --git a/man/pr_run.Rd b/man/pr_run.Rd index fdd279eec..e674b7ed4 100644 --- a/man/pr_run.Rd +++ b/man/pr_run.Rd @@ -4,7 +4,16 @@ \alias{pr_run} \title{Start a server using \code{plumber} object} \usage{ -pr_run(pr, host = "127.0.0.1", port = getOption("plumber.port", NULL)) +pr_run( + pr, + host = "127.0.0.1", + port = getOption("plumber.port", NULL), + ..., + debug = missing_arg(), + docs = missing_arg(), + swaggerCallback = missing_arg(), + quiet = FALSE +) } \arguments{ \item{pr}{A Plumber API. Note: The supplied Plumber API object will also be updated in place as well as returned by the function.} @@ -16,6 +25,24 @@ all IPv4 addresses and "::/0" represents all IPv6 addresses.} \item{port}{A number or integer that indicates the server port that should be listened on. Note that on most Unix-like systems including Linux and Mac OS X, port numbers smaller than 1025 require root privileges.} + +\item{...}{Should be empty.} + +\item{debug}{If \code{TRUE}, it will provide more insight into your API errors. +Using this value will only last for the duration of the run. +If \code{\link[=pr_set_debug]{pr_set_debug()}} has not been called, \code{debug} will default to \code{interactive()} at \code{\link[=pr_run]{pr_run()}} time} + +\item{docs}{Visual documentation value to use while running the API. +This value will only be used while running the router. +If missing, defaults to information previously set with \code{\link[=pr_set_docs]{pr_set_docs()}}. +For more customization, see \code{\link[=pr_set_docs]{pr_set_docs()}} for examples.} + +\item{swaggerCallback}{An optional single-argument function that is called +back with the URL to an OpenAPI user interface when one becomes ready. If +missing, defaults to information set with \code{\link[=pr_set_docs_callback]{pr_set_docs_callback()}}. +This value will only be used while running the router.} + +\item{quiet}{If \code{TRUE}, don't print routine startup messages.} } \description{ \code{port} does not need to be explicitly assigned. @@ -26,7 +53,14 @@ pr() \%>\% pr_run() pr() \%>\% - pr_run(port = 5762) + pr_run( + # manually set port + port = 5762, + # turn off visual documentation + docs = FALSE, + # do not display startup messages + quiet = TRUE + ) } } diff --git a/tests/testthat/test-options.R b/tests/testthat/test-options.R index e9ca6f00c..8fa6e53af 100644 --- a/tests/testthat/test-options.R +++ b/tests/testthat/test-options.R @@ -52,22 +52,31 @@ test_that("all options used are `options_plumber()` parameters", { test_that("Legacy swagger redirect can be disabled", { - with_options(list(), { - options_plumber(legacyRedirects = TRUE) - redirects <- swagger_redirects() - expect_gt(length(redirects), 0) + with_options( + list( + plumber.legacyRedirets = getOption("plumber.legacyRedirects") + ), { + options_plumber(legacyRedirects = TRUE) + redirects <- swagger_redirects() + expect_gt(length(redirects), 0) - options_plumber(legacyRedirects = FALSE) - redirects <- swagger_redirects() - expect_equal(length(redirects), 0) - }) + options_plumber(legacyRedirects = FALSE) + redirects <- swagger_redirects() + expect_equal(length(redirects), 0) + } + ) }) test_that("docs.callback sync plumber.swagger.url", { - with_options(list(), { - options("plumber.swagger.url" = function(api_url) {cat(api_url)}) - opt <- options_plumber(docs.callback = NULL) - expect_null(getOption("plumber.swagger.url")) - expect_null(opt$plumber.docs.callback) - }) + with_options( + list( + plumber.swagger.url = getOption("plumber.swagger.url"), + plumber.docs.callback = getOption("plumber.docs.callback") + ), { + options("plumber.swagger.url" = function(api_url) {cat(api_url)}) + opt <- options_plumber(docs.callback = NULL) + expect_null(getOption("plumber.swagger.url")) + expect_null(opt$plumber.docs.callback) + } + ) }) diff --git a/tests/testthat/test-plumber-run.R b/tests/testthat/test-plumber-run.R new file mode 100644 index 000000000..ffdf829bf --- /dev/null +++ b/tests/testthat/test-plumber-run.R @@ -0,0 +1,136 @@ + +with_interrupt <- function(expr) { + # Causes pr_run() to immediately exit + later::later(httpuv::interrupt) + force(expr) +} + +test_that("quiet=TRUE suppresses startup messages", { + with_interrupt({ + expect_message(pr() %>% pr_run(quiet = TRUE), NA) + }) + with_interrupt({ + expect_message(pr()$run(quiet = TRUE), NA) + }) +}) + +test_that("`docs` does not not permanetly set pr information", { + doc_name <- "swagger" + root <- pr() %>% pr_set_docs(doc_name) + # no docs. do not find printed message + with_interrupt({ + expect_failure( + expect_message({ + root %>% pr_run(docs = FALSE) + }, doc_name) + ) + }) + # no docs. do not find printed message + with_interrupt({ + expect_failure( + expect_message({ + root$run(docs = FALSE) + }, doc_name) + ) + }) + # docs enabled by default. Find printed message + with_interrupt({ + expect_message({ + root %>% pr_run(quiet = FALSE) + }, doc_name) + }) +}) + +test_that("`swaggerCallback` does not not permanetly set pr information", { + skip_if_not_installed("mockery", "0.4.2") + + m <- mockery::mock(TRUE, cycle = TRUE) + m() # call once so that `length(m)` > 0 as `length(m)` represents the number of calls to `m` + root <- pr() %>% pr_set_docs_callback(m) + # m not used + with_interrupt({ + mockery::expect_called(m, 1) + root %>% pr_run(swaggerCallback = NULL) + mockery::expect_called(m, 1) + }) + # m not used + with_interrupt({ + mockery::expect_called(m, 1) + root$run(swaggerCallback = NULL) + mockery::expect_called(m, 1) + }) + # m is used + with_interrupt({ + mockery::expect_called(m, 1) + root %>% pr_run(quiet = FALSE) + mockery::expect_called(m, 2) + }) +}) + +test_that("`swaggerCallback` can be set by option after the pr is created", { + skip_if_not_installed("mockery", "0.4.2") + + m <- mockery::mock(TRUE) + + # must initialize before options are set for this test + root <- pr() + + with_options( + list( + plumber.swagger.url = getOption("plumber.swagger.url"), + plumber.docs.callback = getOption("plumber.docs.callback") + ), + { + # set option after init + options_plumber(docs.callback = m) + with_interrupt({ + mockery::expect_called(m, 0) + pr_run(root) + }) + } + ) + # m is used + mockery::expect_called(m, 1) + +}) + + +### Test does not work as expected with R6 objects. +test_that("`debug` is not set until runtime", { + skip_if_not_installed("mockery", "0.4.2") + + m <- mockery::mock(TRUE, cycle = TRUE) + # https://github.com/r-lib/testthat/issues/734#issuecomment-377367516 + # > It should work if you fully qualify the function name (include the pkgname) + with_mock("plumber:::default_debug" = m, { + root <- pr() + root$getDebug() + mockery::expect_called(m, 1) + + with_interrupt({ + root %>% pr_run(quiet = TRUE) + }) + # increase by 1 + mockery::expect_called(m, 2) + + # listen to set value + with_interrupt({ + root %>% + pr_set_debug(TRUE) %>% + pr_run(quiet = TRUE) + }) + # not updated. stay at 2 + mockery::expect_called(m, 2) + + # listen to run value + with_interrupt({ + root %>% + pr_run(debug = FALSE, quiet = TRUE) + }) + # not updated. stay at 2 + mockery::expect_called(m, 2) + + # TODO test that run(debug=) has preference over pr_set_debug() + }) + +})