tests/testthat/test_io.R

library(civis)
context("io")

# read_civis ------------------------------------------------------------------

test_that("read_civis.sql reads csvs", {
  mock_df <- data.frame(a = 1:2, b = c("sentimental", "centipede"))
  mock_get_sql_runs <- function(...) {
    list(state = "succeeded",
         output = list(list(fileId = 1234))
    )
  }
  mock_sql_job <- function(...) list(script_id = 1337, run_id = 007)
  mock_download_script_results <- function(id, run_id, filename) {
    write.csv(mock_df, file = filename, row.names = FALSE)
    return(filename)
  }
  local_mocked_bindings(
    start_scripted_sql_job = mock_sql_job,
    scripts_post_sql_runs = function(...) list(id = 1001),
    scripts_get_sql_runs = mock_get_sql_runs,
    files_get = function(...) list(fileSize = 4E9),
    download_script_results = mock_download_script_results,
    stop_for_status = function(...) return(TRUE)
  )
  expect_equal(mock_df, read_civis(x = "lazy", database = "jellyfish"))
  expect_equal(mock_df, read_civis(sql("SELECT * FROM lazy"),
                                   database = "jellyfish"))
})

test_that("read_civis.sql produces catchable error when query returns no rows", {
  no_results_resp <- list(state = "succeeded", output = list())

  mock_sql_job <- function(...) list(script_id = 561, run_id = 43)
  local_mocked_bindings(
    start_scripted_sql_job = mock_sql_job,
    scripts_get_sql_runs = function(...) no_results_resp
  )
  try_err <- try(read_civis(sql("SELECT 0"), database = "arrgh"), silent = TRUE)
  expect_true("empty_result_error" %in% class(attr(try_err, "condition")))
})

test_that("read_civis.numeric reads a csv", {
  d <- data.frame(a = 1:2, b = c("sentimental", "centipede"))
  mock_response <- function(...) {
    structure(list(url = "http://www.fakeurl.com", status_code = 200),
              class = "response")
  }
  local_mocked_bindings(
    files_get =  function(...) list(fileUrl = "fakeurl.com"),
    download_civis = function(id, fn) write.csv(d, file = fn),
    httr_RETRY = mock_response,
  )
  expect_equal(d, read_civis(123, using = read.csv, row.names = 1))
})

test_that("read_civis.numeric fails for NA", {
  msg <- "File ID cannot be NA."
  expect_error(read_civis(as.numeric(NA)), msg)
})

test_that("read_civis.civis_script using = NULL", {
  mock_output <- list(list(name = 'asdf', objectId = 1, objectType = 'JSONValue', value = 'a'),
                      list(name = 'fake', objectId = 2, objectType = 'JSONValue', value = 'b'),
                      list(name = 'file_fake', objectId = 3, objectType = 'File'))
  vals <- local_mocked_bindings(
    jobs_get = function(...) list(type = 'JobTypes::ContainerDocker'),
    scripts_list_containers_runs_outputs = function(...) mock_output
  )
  expect_equal(read_civis(civis_script(1,1), using = NULL),
               list(asdf = 'a', fake = 'b'))
  expect_equal(read_civis(civis_script(1,1), using = NULL, regex = 'fake'),
               list(fake = 'b'))
})

test_that("read_civis.civis_script with using", {
  mock_output <- list(list(name = 'asdf', objectId = 1, objectType = 'JSONValue', value = 'a'),
                      list(name = 'lol', objectId = 2, objectType = 'File'),
                      list(name = 'fake', objectId = 3, objectType = 'File'))
  local_mocked_bindings(
    jobs_get = function(...) list(type = 'JobTypes::ContainerDocker'),
    scripts_list_containers_runs_outputs = function(...) mock_output,
    # returns first arg of read_civis.numeric, which is the objectId
    read_civis.numeric = function(...) list(...)[[1]]
  )
  expect_equal(read_civis(civis_script(1, 1), using = I),
                list(lol = 2, fake = 3))
  expect_equal(read_civis(civis_script(1, 1), regex = 'fake', using = I),
                list(fake = 3))
})


# write_civis -----------------------------------------------------------------

test_that("write_civis.character returns meta data if successful", {
  mock_df <- cbind.data.frame(a = c(1,2), b = c("cape-cod", "clams"))
  write("", file = "mockfile")
  local_mocked_bindings(
    start_import_job = function(...) {
      list(uploadUri = "fake", id = 1)
    },
    default_credential = function(...) 1234,
    tables_post_refresh = function(id) "",
    imports_post_files_runs = function(...) list(""),
    imports_get_files_runs = function(...) list(state = "succeeded"),
    httr_RETRY = function(...) list(status_code = 200),
  )
  res <- write_civis("mockfile", "mock.table", "mockdb")

  unlink("mockfile")
  expect_equal(get_status(res), "succeeded")
})

test_that("write_civis.character fails if file doesn't exist", {
  mock_df <- cbind.data.frame(a = c(1,2), b = c("cape-cod", "clams"))
  local_mocked_bindings(
    start_import_job = function(...) {
      list(uploadUri = "fake")
    },
    imports_post_files_runs = function(...) list(""),
    imports_get_files_runs = function(...) list(state = "succeeded"),
    httr_RETRY = function(...) list(status_code = 200),
  )
  err_msg <- tryCatch(write_civis("mockfile", "mock.table", "mockdb"), error = function(e) e$message)
  msg <- "file.exists(x) is not TRUE"
  expect_equal(err_msg, msg)
})

test_that("write_civis.data.frame returns meta data if successful", {
  mock_df <- cbind.data.frame(a = c(1,2), b = c("cape-cod", "clams"))
   local_mocked_bindings(
    start_import_job = function(...) {
      list(uploadUri = "fake", id = 1)
    },
    default_credential = function(...) 1,
    tables_post_refresh = function(id) "",
    imports_post_files_runs = function(...) list(""),
    imports_get_files_runs = function(...) list(state = "succeeded"),
    tables_list = function(...) 1,
    httr_RETRY = function(...) list(status_code = 200),
  )
  res <- write_civis(mock_df, "mock.table", "mockdb")

  expect_equal(get_status(res), "succeeded")
})

test_that("write_civis.character warns under failure", {
  mock_df <- cbind.data.frame(a = c(1,2), b = c("cape-cod", "clams"))
  local_mocked_bindings(
    start_import_job = function(...) {
      list(uploadUri = "fake", id = -999)
    },
    imports_post_files_runs = function(...) "",
    imports_get_files_runs = function(...) list(state = "failed"),
    httr_RETRY = function(...) list(status_code = 200),
    httr_content = function(...) "error",
  )
  expect_error(write_civis("mockfile", "mock.table", "mockdb"))
})

test_that("write_civis.character calls imports endpoints correctly", {
  ipf <- mock(list(id = 4))
  fn <- tempfile(fileext = ".csv")
  file.create(fn)
  local_mocked_bindings(
    get_database_id = function(...) 32,
    default_credential = function(...) 999,
    imports_post_files = ipf,
    imports_post_files_runs = function(...) list(id = 44),
    imports_get_files_runs = function(...) list(state = "succeeded"),
    httr_RETRY = mock(list(status_code = 200)),
  )
  res <- write_civis(fn, "mock.table", "mockdb", credential_id = 1)
  expect_equal(get_status(res), "succeeded")
  expect_args(ipf, n = 1,
              schema = 'mock',
              name = 'table',
              remote_host_id = 32,
              credential_id = 1,
              max_errors = NULL,
              existing_table_rows = "fail",
              diststyle = NULL,
              distkey = NULL,
              sortkey1 = NULL,
              sortkey2 = NULL,
              column_delimiter = "comma",
              firstRowIsHeader = TRUE,
              hidden = TRUE
  )
})

test_that("write_civis.numeric calls imports endpoints correctly", {
  ip <- mock(list(id = 4))
  local_mocked_bindings(
    get_database_id = function(...) 32,
    default_credential = function(...) 999,
    imports_post =  ip,
    imports_post_syncs = mock(),
    jobs_post_runs = function(...) list(id = 4),
    jobs_get_runs = function(...) list(state = "succeeded")
  )
  res <- write_civis(1234, "mock.table", "mockdb", credential_id = 1,
                     import_args = list(verifyTableRowCounts = TRUE))
  expect_equal(get_status(res), "succeeded")
  expect_args(ip, n = 1,
                import_name = "CSV import to mock.table",
                sync_type = 'AutoImport',
                is_output = FALSE,
                destination = list(remote_host_id = 32, credential_id = 1),
                hidden = TRUE)
  expect_args(civis::imports_post_syncs, n = 1,
              id = 4,
              list(file = list(id = 1234)),
              destination = list(database_table =
                                    list(schema = "mock", table = "table")),
              advanced_options = list(
                max_errors = NULL,
                existing_table_rows = "fail",
                distkey = NULL,
                diststyle = NULL,
                sortkey1 = NULL,
                sortkey2 = NULL,
                column_delimiter = "comma",
                firstRowIsHeader = TRUE,
                verifyTableRowCounts = TRUE
              ))
})

test_that("write_civis fails if no db given and default not provided", {
  local_mocked_bindings(
    get_default_database = function(...) NULL
  )
  err_msg <- tryCatch(write_civis(iris), error = function(e) e$message)
  db_err <- tryCatch(get_db(NULL), error = function(e) e$message)
  expect_equal(err_msg, db_err)
})

test_that("write_civis.numeric fails for NA", {
  msg <- "File ID cannot be NA."
  expect_error(write_civis(as.numeric(NA)), msg)
})

test_that("write_civis_file fails if file doesn't exist", {
  regexp <- "File 'x' does not exist.*"
  expect_error(write_civis_file("asdf"), regexp)
})

test_that("write_civis_file fails if character vector length 2 is passed", {
  regexp <- "'x' has length > 1.*"
  expect_error(write_civis_file(c('fake', 'paths')), regexp)
})

test_that("write_civis_file.character returns a file id", {
  write("", "mockfile.txt")
  local_mocked_bindings(
    files_post = function(...) list(uploadFields = list("fakeurl.com"), id = 5),
    httr_upload_file = function(...) "the file",
    httr_RETRY = function(...) structure(list(status_code = 200), class = "response"),
  )
  expect_equal(write_civis_file("mockfile.txt", name = "mockfile.txt"), 5)
  unlink("mockfile.txt")
})

test_that("write_civis_file returns a file id", {
  mock_df <- data.frame(a = c(1,2), b = c("cape-cod", "clams"))
  local_mocked_bindings(
    files_post = function(...) list(uploadFields = list("fakeurl.com"), id = 5),
    httr_upload_file = function(...) "the file",
    httr_RETRY = function(...) structure(list(status_code = 200), class = "response"),
  )
  expect_equal(write_civis_file(mock_df), 5)
  expect_equal(write_civis_file(as.list(mock_df)), 5)
  expect_equal(write_civis_file(1:3), 5)
})

test_that("write_civis_file calls multipart_unload for big files", {
  fake_file_size <- mock(file.size)
  mockery::stub(write_civis_file.character, "file.size", MIN_MULTIPART_SIZE + 1)
  fn <- tempfile()
  file.create(fn)
  local_mocked_bindings(
    multipart_upload = function(...) 1
  )
  expect_equal(write_civis_file(fn, name = "asdf"), 1)
  unlink(fn)
})

test_that("write_civis_file.data.frame uploads a csv", {
  m <- mock()
  local_mocked_bindings(
    with_tempfile = m,
    write_civis_file.character = function(...) 1
  )
  write_civis_file(iris)
  # call the temporary function given to with_tempfile
  mock_args(m)[[1]][[1]]('tmp.csv')
  expect_equal(read.csv('tmp.csv', stringsAsFactors = TRUE), iris)
  unlink('tmp.csv')
})

# download_civis --------------------------------------------------------------

test_that("download_civis raises an error if destination file is not specified", {
  expect_error(download_civis("mock.table", "mockdb"),
               "argument \"file\" is missing")
})

test_that("download_civis raises an error if destination file already exists", {
  local_file <- "my_table.csv"
  file.create(local_file)
  expect_error(download_civis("mock.table", "mockdb", local_file, overwrite = FALSE),
               "File already exists")
  file.remove(local_file) # Clean up after testing
})

test_that("download_civis.numeric fails for NA", {
  msg <- "File ID cannot be NA."
  expect_error(write_civis(as.numeric(NA)), msg)
})


# query_civis -----------------------------------------------------------------

test_that("query_civis returns object from await", {
  qp <- mockery::mock()
  local_mocked_bindings(
    get_database_id = function(...) TRUE,
    default_credential = function(...) 1,
    queries_post = qp,
    queries_get = function(...) list(state = 'succeeded')
  )
  expect_equal(get_status(query_civis("query", "database", credential = 10)), 'succeeded')
  expect_equal(mockery::mock_args(qp)[[1]]$credential, 10)
})

test_that("query_civis.numeric fails for NA", {
  msg <- "Query ID cannot be NA."
  expect_error(query_civis(as.numeric(NA)), msg)
})

test_that("query_civis_file.sql works", {
  qp <- mockery::mock()
  local_mocked_bindings(
    get_database_id = function(...) TRUE,
    get_db = function(...) "asdf",
    default_credential = function(...) 1,
    start_scripted_sql_job = qp,
    scripts_get_sql_runs = function(...) list(state = "succeeded",
                                              output = list(list(fileId = 1)))
  )
  expect_equal(query_civis_file(sql("asdf"), credential = 10), 1)
  expect_equal(mockery::mock_args(qp)[[1]]$credential, 10)
})

test_that("query_civis_file.character errors if not schema.tablename", {
  msg <- 'Argument x should be "schema.tablename". Did you mean x = sql("...")?'
  expect_error(query_civis_file("select asdf"), msg)
})

test_that("query_civis_file.numeric works", {
  local_mocked_bindings(
    scripts_post_sql_runs = function(...) list(id = 333),
    scripts_get_sql_runs = function(...) list(state = "succeeded",
                                              output = list(list(fileId = 1)))
  )
  expect_equal(query_civis_file(234), 1)
})

test_that("query_civis_file.numeric fails for NA", {
  msg <- "Query ID cannot be NA"
  expect_error(query_civis(as.numeric(NA), msg))
})

test_that("transfer_table succeeds", {
  local_mocked_bindings(
    default_credential = function(...) 1,
    get_database_id = function(...) 32,
    imports_post = function(...) list(id = 999),
    imports_post_syncs = function(...) NULL,
    imports_post_runs = function(...) list(runId = 999),
    imports_get_files_runs = function(...) list(state = "succeeded")
  )
  res <- transfer_table("db1", "db2", "sc.tb1", "sc.tb2")
  expect_equal(get_status(res), "succeeded")
})

# utils functions -------------------------------------------------------------

test_that("multipart_upload returns file_id", {
  fn <- tempfile()
  d <- data.frame(a = 1:5, b = 5:1)
  write.csv(d, fn, row.names = FALSE)
  local_mocked_bindings(
    upload_one = function(...) NULL,
    files_post_multipart = function(...) list(id = 1, uploadUrls = "url"),
    files_post_multipart_complete = function(...) NULL,
    future_value = function(...) NULL,
  )
  id <- multipart_upload(fn, name = "asdf")
  expect_equal(id, 1)
})

test_that("write_chunks splits files", {
  # csv
  d <- data.frame(a = 1:5, b = 5:1)
  fn <- tempfile(fileext = ".txt")
  write.csv(d, fn, row.names = FALSE)
  fl <- write_chunks(fn, chunk_size = file.size(fn)/4)
  expect_equal(length(fl), 4)

  the_text <- paste0(unlist(lapply(fl, function(f) {
    readChar(f, file.size(f))
  })), collapse = "")
  ans <- read.csv(textConnection(the_text))
  expect_equal(ans, d)

  # rds; again, we have to really just stitch the files together to read it back.
  fn <- tempfile(fileext = ".rds")
  saveRDS(d, fn)
  fl <- write_chunks(fn, chunk_size = file.size(fn)/4)

  the_bin <- unlist(lapply(fl, function(f) {
    readBin(f, what = "raw", file.size(f))
  }))
  zz <- rawConnection(the_bin)
  ans <- readRDS(gzcon(zz))
  close(zz)
  expect_equal(ans, d)
})

test_that("get_db returns default database or an error", {
  expect_equal(get_db("sea_creatures"), "sea_creatures")

  options(civis.default_db = "sea_creatures")
  expect_equal(get_db(NULL), "sea_creatures")

  expected_msg <- c("Argument database is NULL and options(\"civis.default_db\") not set. Set this option using options(civis.default_db = \"my_database\")")
  local_mocked_bindings(
    get_default_database = function(...) NULL
  )
  actual_msg <- tryCatch(get_db(NULL), error = function(e) e$message)
  expect_equal(expected_msg, actual_msg)
})

test_that("delimiter_name_from_string catches bad input", {
  e <- "Delimiter must be one of ',', '|', '\t'"
  comma <- delimiter_name_from_string(",")
  pipe <- delimiter_name_from_string("|")
  tab <- delimiter_name_from_string("\t")
  expect_equal(comma, "comma")
  expect_equal(pipe, "pipe")
  expect_equal(tab, "tab")
  expect_error(delimiter_name_from_string(":"), e)
})


test_that("start_import_job parses table correctly", {
  local_mocked_bindings(
    get_database_id = function(...) -999,
    default_credential = function(...) "fake",
    imports_post_files = function(...) {
      args <- list(...)
      list(schema = args[[1]], table = args[[2]])
    }
  )
  expect_equal(
    start_import_job("mockdb", "mock.table", if_exists = "append",
                      NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL),
    list(schema = "mock", table = "table")
  )
})

test_that("start_import_job checks if_exists value", {
  error_msg <- 'if_exists must be set to "fail", "truncate", "append", or "drop"'
  local_mocked_bindings(
    get_database_id = function(...) -999,
    default_credential = function(...) "fake",
    imports_post_files = function(...) {
      args <- list(...)
      list(schema = args[[1]], table = args[[2]])
    }
  )
  expect_error(
    start_import_job("mockdb", "mock.table", if_exists = "do nothing",
                      NULL, NULL, NULL, NULL),
    error_msg
  )
})

test_that("download_script_results returns sensible errors", {
  error <- "Query produced no output. \\(script_id = 561, run_id = 43\\)"
  mock_get_run <- function(script_id, run_id) list(script_id = script_id, run_id = run_id)
  local_mocked_bindings(
    scripts_get_sql_runs = mock_get_run
  )
  expect_error(download_script_results(561, 43, "some_file"), error)
})

Try the civis package in your browser

Any scripts or data that you put into this service are public.

civis documentation built on Aug. 30, 2025, 1:08 a.m.