context("OpenAPI")

test_that("plumberToApiType works", {
  expect_equal(plumberToApiType("bool"), "boolean")
  expect_equal(plumberToApiType("logical"), "boolean")

  expect_equal(plumberToApiType("double"), "number")
  expect_equal(plumberToApiType("numeric"), "number")

  expect_equal(plumberToApiType("int"), "integer")

  expect_equal(plumberToApiType("character"), "string")

  expect_equal(plumberToApiType("df"), "object")
  expect_equal(plumberToApiType("list"), "object")
  expect_equal(plumberToApiType("data.frame"), "object")

  expect_warning({
    expect_equal(plumberToApiType("flargdarg"),  defaultApiType)
  }, "Unrecognized type:")
})

test_that("response attributes are parsed", {
  lines <- c(
    "#' @get /",
    "#' @response 201 This is response 201",
    "#' @response 202 Here's second",
    "#' @response 203 Here's third",
    "#' @response default And default")
  b <- plumbBlock(length(lines), lines)
  expect_length(b$responses, 4)
  expect_equal(b$responses$`201`, list(description="This is response 201"))
  expect_equal(b$responses$`202`, list(description="Here's second"))
  expect_equal(b$responses$`203`, list(description="Here's third"))
  expect_equal(b$responses$default, list(description="And default"))

  b <- plumbBlock(1, "")
  expect_null(b$responses)
})

test_that("params are parsed", {
  lines <- c(
    "#' @get /",
    "#' @param test Test docs",
    "#' @param required:character* Required param",
    "#' @param another:int Another docs",
    "#' @param multi:[int]* Required array param")
  b <- plumbBlock(length(lines), lines)
  expect_length(b$params, 4)
  expect_equal(b$params$another, list(desc="Another docs", type="integer", required=FALSE, isArray = FALSE))
  expect_equal(b$params$test, list(desc="Test docs", type=defaultApiType, required=FALSE, isArray = FALSE))
  expect_equal(b$params$required, list(desc="Required param", type="string", required=TRUE, isArray = FALSE))
  expect_equal(b$params$multi, list(desc="Required array param", type="integer", required=TRUE, isArray = TRUE))

  b <- plumbBlock(1, "")
  expect_null(b$params)
})

# TODO
#test_that("endpointSpecification works", {
#})

test_that("getApiSpec works with mounted routers", {
  # parameter in path
  pr1 <- pr()
  pr1$handle("GET", "/nested/:path/here", function(){})
  pr1$handle("POST", "/nested/:path/here", function(){})

    # static file handler
  stat <- PlumberStatic$new(".")

  # multiple entries
  pr2 <- pr()
  pr2$handle("GET", "/something", function(){})
  pr2$handle("POST", "/something", function(){})
  pr2$handle("GET", "/", function(){})

  # test with a filter
  pr3 <- pr()
  pr3$filter("filter1", function(){})
  pr3$handle("POST", "/else", function(){}, "filter1")
  pr3$handle("PUT", "/else", function(){})
  pr3$handle("GET", "/", function(){})

  # nested mount
  pr4 <- pr()
  pr4$handle("GET", "/completely", function(){})

  # trailing slash in route
  pr5 <- pr()
  pr5$handle("GET", "/trailing_slash/", function(){})

  # ├──/nested
  # │  ├──/:path
  # │  │  └──/here (GET, POST)
  # ├──/static
  # ├──/sub2
  # │  ├──/something (GET, POST)
  # │  ├──/ (GET)
  # │  ├──/sub3
  # │  │  ├──/else (POST, PUT)
  # │  │  └──/ (GET)
  # ├──/sub4
  # │  ├──/completely (GET)
  # │  ├──/
  # │  │  └──/trailing_slash (GET)
  pr1$mount("/static", stat)
  pr2$mount("/sub3", pr3)
  pr1$mount("/sub2", pr2)
  pr1$mount("/sub4", pr4)
  pr4$mount("/", pr5)

  paths <- names(pr1$getApiSpec()$paths)
  expect_length(paths, 7)
  expect_equal(paths, c("/nested/:path/here", "/sub2/something",
    "/sub2/", "/sub2/sub3/else", "/sub2/sub3/", "/sub4/completely",
    "/sub4/trailing_slash/"
  ))
})

test_that("responsesSpecification works", {
  # Empty
  r <- responsesSpecification(NULL)
  expect_equal(r, defaultResponse)

  # Response constructor actually defaults to NA, so that's an important case, too
  r <- responsesSpecification(NA)
  expect_equal(r, defaultResponse)

  # Responses with no default
  customResps <- list("400" = list())
  endpts <- plumber::PlumberEndpoint$new(
    path = "/a",
    responses = customResps,
    verbs = "GET",
    expr = function() {},
    envir = new.env(parent = globalenv()))
  r <- responsesSpecification(endpts)
  expect_length(r, 4)
  expect_equal(r$default, defaultResponse$default)
  expect_equal(r$`400`, customResps$`400`)
})

test_that("parametersSpecification works", {
  ep <- list(id=list(desc="Description", type="integer", required=FALSE),
             id2=list(desc="Description2", required=FALSE), # No redundant type specification
             make=list(desc="Make description", type="string", required=FALSE),
             prices=list(desc="Historic sell prices", type="numeric", required = FALSE, isArray = TRUE),
             claims=list(desc="Insurance claims", type="object", required = FALSE))
  pp <- data.frame(name=c("id", "id2", "owners"), type=c("int", "int", "chr"), isArray = c(FALSE, FALSE, TRUE), stringsAsFactors = FALSE)

  params <- parametersSpecification(ep, pp)
  expect_equal(params$parameters[[1]],
               list(name="id",
                    description="Description",
                    `in`="path",
                    required=TRUE, # Made required b/c path arg
                    schema = list(
                      type="integer",
                      format="int64",
                      default=NULL)))
  expect_equal(params$parameters[[2]],
               list(name="id2",
                    description="Description2",
                    `in`="path",
                    required=TRUE, # Made required b/c path arg
                    schema = list(
                      type="integer",
                      format="int64",
                      default=NULL)))
  expect_equal(params$parameters[[3]],
               list(name="make",
                    description="Make description",
                    `in`="query",
                    required=FALSE,
                    schema = list(
                      type="string",
                      format=NULL,
                      default=NULL)))
  expect_equal(params$parameters[[4]],
               list(name="prices",
                    description="Historic sell prices",
                    `in`="query",
                    required=FALSE,
                    schema = list(
                      type="array",
                      items= list(
                        type="number",
                        format="double"),
                      default = NULL),
                    style="form",
                    explode=TRUE))
  expect_equal(params$parameters[[5]],
               list(name="owners",
                    description=NULL,
                    `in`="path",
                    required=TRUE,
                    schema = list(
                      type="array",
                      items= list(
                        type="string",
                        format=NULL),
                      default = NULL),
                    style="simple",
                    explode=FALSE))
  expect_equal(params$requestBody,
               list(content = list(
                 `application/json` = list(
                   schema = list(
                     type = "object",
                     properties = list(
                       claims = list(
                         type = "object",
                         format = NULL,
                         example = NULL,
                         description = "Insurance claims")))))))

  # If id were not a path param it should not be promoted to required
  params <- parametersSpecification(ep, NULL)
  idParam <- params$parameters[[which(vapply(params$parameters, `[[`, character(1), "name") == "id")]]
  expect_equal(idParam$required, FALSE)
  expect_equal(idParam$schema$type, "integer")

  for (param in params$parameters) {
    if (param$schema$type != "array") {
      expect_equal(length(param), 5)
    } else {
      expect_equal(length(param), 7)
    }
  }

  # Check if we can pass a single path parameter without a @param line match
  params <- parametersSpecification(NULL, pp[3,])
  expect_equal(params$parameters[[1]],
               list(name="owners",
                    description=NULL,
                    `in`="path",
                    required=TRUE,
                    schema = list(
                      type="array",
                      items= list(
                        type="string",
                        format=NULL),
                      default=NULL),
                    style="simple",
                    explode=FALSE))

  params <- parametersSpecification(NULL, NULL)
  expect_equal(sum(sapply(params, length)), 0)
})

test_that("api kitchen sink", {

  skip_on_cran()
  skip_on_bioc()
  skip_on_os(setdiff(c("windows", "mac", "linux", "solaris"), c("mac", "linux")))

  ## install brew - https://brew.sh/
  # /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  ## install yarn
  # brew install yarn
  ## install yarn
  # yarn add swagger-ui

  # yarn install

  skip_if_not(nzchar(Sys.which("node")), "node not installed")
  skip_if_not(nzchar(Sys.which("npm")), "`npm` system dep not installed")

  for_each_plumber_api(validate_api_spec, verbose = FALSE)

  # TODO test more situations
})

test_that("multiple variations in function extract correct metadata", {
  dummy <- function(var0 = 420.69,
                    var1,
                    var2 = c(1L, 2L),
                    var3 = rnorm,
                    var4 = NULL,
                    var5 = FALSE,
                    var6 = list(name = c("luke", "bob"), lastname = c("skywalker", "ross")),
                    var7 = new.env(parent = .GlobalEnv),
                    var8 = list(a = 2, b = mean, c = new.env(parent = .GlobalEnv))) {}
  funcParams <- getArgsMetadata(dummy)
  expect_identical(sapply(funcParams, `[[`, "required"),
                   c(var0 = FALSE, var1 = TRUE, var2 = FALSE, var3 = FALSE, var4 = FALSE,
                     var5 = FALSE, var6 = FALSE, var7 = FALSE, var8 = FALSE))
  expect_identical(lapply(funcParams, `[[`, "default"),
                   list(var0 = 420.69, var1 = NA, var2 = 1L:2L, var3 = NA, var4 = NA, var5 = FALSE,
                        var6 = list(name = c("luke", "bob"), lastname = c("skywalker", "ross")), var7 = NA, var8 = NA))
  expect_identical(lapply(funcParams, `[[`, "example"),
                   list(var0 = 420.69, var1 = NA, var2 = 1L:2L, var3 = NA, var4 = NA, var5 = FALSE,
                        var6 = list(name = c("luke", "bob"), lastname = c("skywalker", "ross")), var7 = NA, var8 = NA))
  expect_identical(lapply(funcParams, `[[`, "isArray"),
                   list(var0 = defaultIsArray, var1 = defaultIsArray, var2 = TRUE,
                        var3 = defaultIsArray, var4 = defaultIsArray,
                        var5 = defaultIsArray, var6 = TRUE,
                        var7 = defaultIsArray, var8 = defaultIsArray))
  expect_identical(lapply(funcParams, `[[`, "type"),
                   list(var0 = "number", var1 = defaultApiType, var2 = "integer", var3 = defaultApiType, var4 = defaultApiType,
                        var5 = "boolean", var6 = "object", var7 = defaultApiType, var8 = defaultApiType))

})

test_that("priorize works as expected", {
  expect_identical("abc", priorizeProperty(structure("zzz", default = TRUE), NULL, "abc"))
  expect_identical(NULL, priorizeProperty(NULL, NULL, NULL))
  expect_identical(structure("zzz", default = TRUE), priorizeProperty(structure("zzz", default = TRUE), NULL, NA))
  expect_identical(NULL, priorizeProperty())
})

test_that("custom spec works", {
  pr <- pr()
  pr$handle("POST", "/func1", function(){})
  pr$handle("GET", "/func2", function(){})
  pr$handle("GET", "/func3", function(){})
  customSpec <- function(spec) {
    custom <- list(info = list(description = "My Custom Spec", title = "This is only a test"))
    return(utils::modifyList(spec, custom))
  }
  pr$setApiSpec(customSpec)
  spec <- pr$getApiSpec()
  expect_equal(spec$info$description, "My Custom Spec")
  expect_equal(spec$info$title, "This is only a test")
  expect_equal(class(spec$openapi), "character")
})

test_that("no params plumber router still produces spec when there is a func params", {
  pr <- pr()
  handler <- function(num) { sum(as.integer(num)) }
  pr$handle("GET", "/sum", handler, serializer = serializer_json())
  spec <- pr$getApiSpec()
  expect_equal(spec$paths$`/sum`$get$parameters[[1]]$name, "num")
})

test_that("Response content type set with serializer", {
  a <- pr()
  pr_get(a, "/json", function() {"OK"}, serializer = serializer_json())
  pr_get(a, "/csv", function() {"OK"}, serializer = serializer_csv())
  spec <- a$getApiSpec()
  expect_equal(spec$paths$`/json`$get$responses$`200`$content, list("application/json" = list(schema = list(type = "object"))))
  expect_equal(spec$paths$`/csv`$get$responses$`200`$content, list("text/csv" = list(schema = list(type = "string"))))
})

test_that("Api spec can be set using a file path", {
  pr <- pr() %>% pr_set_api_spec(test_path("files/openapi.yaml"))
  expect_equal(pr$getApiSpec()$paths$`/health-check`$get$summary, " Determine if the API is running and listening as expected")
})

test_that("RemoveNAorNULLs preserve nested examples", {
  a <- list(schema = list(example = list(a = 5, b = list(NULL))), examples = list(a = 5, value = list(NULL)))
  expect_identical(removeNaOrNulls(a), a)

  a <- list(schema = list(value = list(NULL), b = list(NULL)))
  expect_identical(removeNaOrNulls(a), list(schema = list(value = list(), b = list())))
})
