tests/testthat/test-publish.R

# Tests for S3 publishing functions
# Note: Most S3 tests require credentials and are skipped in CI

test_that(".resolve_s3_connection finds explicit connection", {
  skip_if_not_installed("aws.s3")

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create config with S3 connection
  config <- list(
    default = list(
      connections = list(
        storage_buckets = list(
          my_s3 = list(
            bucket = "test-bucket",
            region = "us-east-1",
            prefix = "test-prefix"
          )
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  # Set credentials in environment
  withr::local_envvar(
    AWS_ACCESS_KEY_ID = "test-key",
    AWS_SECRET_ACCESS_KEY = "test-secret"
  )

  result <- framework:::.resolve_s3_connection("my_s3")

  expect_equal(result$bucket, "test-bucket")
  expect_equal(result$region, "us-east-1")
  expect_equal(result$prefix, "test-prefix")
  expect_equal(result$access_key, "test-key")
  expect_equal(result$secret_key, "test-secret")
})


test_that(".resolve_s3_connection finds default connection", {
  skip_if_not_installed("aws.s3")

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create config with default S3 connection
  config <- list(
    default = list(
      connections = list(
        storage_buckets = list(
          s3_backup = list(
            bucket = "backup-bucket",
            region = "us-west-2"
          ),
          s3_primary = list(
            bucket = "primary-bucket",
            region = "us-east-1",
            default = TRUE
          )
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  withr::local_envvar(
    AWS_ACCESS_KEY_ID = "test-key",
    AWS_SECRET_ACCESS_KEY = "test-secret"
  )

  # Should find the default connection
  result <- framework:::.resolve_s3_connection(NULL)

  expect_equal(result$bucket, "primary-bucket")
  expect_equal(result$region, "us-east-1")
})


test_that(".resolve_s3_connection errors when connection not found", {
  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  config <- list(
    default = list(
      connections = list(
        framework = list(
          driver = "sqlite",
          database = "framework.db"
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  expect_error(
    framework:::.resolve_s3_connection("nonexistent"),
    "No S3 connections configured|not found"
  )
})


test_that(".resolve_s3_connection errors when credentials are missing", {
  skip_if_not_installed("aws.s3")

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # S3 connection exists but no credentials
  config <- list(
    default = list(
      connections = list(
        storage_buckets = list(
          my_s3 = list(
            bucket = "test-bucket",
            region = "us-east-1"
          )
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  expect_error(
    framework:::.resolve_s3_connection(NULL),
    "S3 credentials not found"
  )
})


test_that(".resolve_s3_connection errors for non-S3 connection", {
  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  config <- list(
    default = list(
      connections = list(
        framework = list(
          driver = "sqlite",
          database = "framework.db"
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  expect_error(
    framework:::.resolve_s3_connection("framework"),
    "No S3 connections configured|not an S3 connection"
  )
})


test_that(".resolve_s3_connection errors when missing credentials", {
  skip_if_not_installed("aws.s3")

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  config <- list(
    default = list(
      connections = list(
        storage_buckets = list(
          my_s3 = list(
            bucket = "test-bucket",
            region = "us-east-1"
          )
        )
      )
    )
  )

  yaml::write_yaml(config, "settings.yml")

  # Clear any existing credentials
  withr::local_envvar(
    AWS_ACCESS_KEY_ID = "",
    AWS_SECRET_ACCESS_KEY = ""
  )

  expect_error(
    framework:::.resolve_s3_connection("my_s3"),
    "credentials not found"
  )
})


test_that(".guess_content_type returns correct MIME types", {
  expect_equal(framework:::.guess_content_type("test.html"), "text/html")
  expect_equal(framework:::.guess_content_type("test.css"), "text/css")
  expect_equal(framework:::.guess_content_type("test.js"), "application/javascript")
  expect_equal(framework:::.guess_content_type("test.json"), "application/json")
  expect_equal(framework:::.guess_content_type("test.csv"), "text/csv")
  expect_equal(framework:::.guess_content_type("test.png"), "image/png")
  expect_equal(framework:::.guess_content_type("test.jpg"), "image/jpeg")
  expect_equal(framework:::.guess_content_type("test.pdf"), "application/pdf")
  expect_equal(framework:::.guess_content_type("test.rds"), "application/octet-stream")
  expect_equal(framework:::.guess_content_type("test.unknown"), "application/octet-stream")
})


test_that(".s3_public_url generates correct AWS URLs", {
  s3_config <- list(
    bucket = "my-bucket",
    region = "us-east-1",
    prefix = "",
    endpoint = NULL
  )

  url <- framework:::.s3_public_url("path/to/file.html", s3_config)
  expect_equal(url, "https://my-bucket.s3.us-east-1.amazonaws.com/path/to/file.html")
})


test_that(".s3_public_url handles custom endpoints", {
  s3_config <- list(
    bucket = "my-bucket",
    region = "us-east-1",
    prefix = "",
    endpoint = "https://minio.example.com"
  )

  url <- framework:::.s3_public_url("path/to/file.html", s3_config)
  expect_equal(url, "https://minio.example.com/my-bucket/path/to/file.html")
})


test_that("publish validates source exists", {
  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create minimal config
  config <- list(
    default = list(
      connections = list(
        my_s3 = list(driver = "s3", bucket = "test", region = "us-east-1", default = TRUE)
      )
    )
  )
  yaml::write_yaml(config, "config.yml")

  expect_error(
    publish("nonexistent_file.txt"),
    "Source not found"
  )
})


test_that("publish_notebook validates file extension", {
  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create a non-qmd file
  writeLines("test", "test.R")

  expect_error(
    publish_notebook("test.R"),
    "extension"
  )
})


test_that("publish_data handles data frames", {
  skip_if_not_installed("aws.s3")

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create config with S3 connection
  config <- list(
    default = list(
      connections = list(
        my_s3 = list(
          driver = "s3",
          bucket = "test-bucket",
          region = "us-east-1",
          default = TRUE
        )
      )
    )
  )
  yaml::write_yaml(config, "config.yml")

  # This will fail at S3 upload (no real credentials), but we're testing
  # that data frame handling works up to that point
  withr::local_envvar(
    AWS_ACCESS_KEY_ID = "test",
    AWS_SECRET_ACCESS_KEY = "test"
  )

  df <- data.frame(x = 1:3, y = letters[1:3])

  # Should fail at S3 upload, not at data frame handling
expect_error(
    publish_data(df, "test.csv"),
    "S3|upload|403|Access Denied|Forbidden|InvalidAccessKeyId"
  )
})


# Integration tests - only run if S3_TEST_BUCKET is set
test_that("S3 integration tests", {
  skip_if(
    Sys.getenv("S3_TEST_BUCKET") == "",
    "S3_TEST_BUCKET not set - skipping integration tests"
  )

  test_dir <- create_test_dir()
  old_wd <- getwd()
  on.exit({
    setwd(old_wd)
    cleanup_test_dir(test_dir)
  })

  setwd(test_dir)

  # Create config with test bucket
  config <- list(
    default = list(
      connections = list(
        test_s3 = list(
          driver = "s3",
          bucket = Sys.getenv("S3_TEST_BUCKET"),
          region = Sys.getenv("S3_TEST_REGION", "us-east-1"),
          prefix = paste0("framework-test-", format(Sys.time(), "%Y%m%d%H%M%S")),
          default = TRUE
        )
      )
    )
  )
  yaml::write_yaml(config, "config.yml")

  # Test s3_test()
  expect_true(s3_test())

  # Test publish() with a simple file
  writeLines("Hello, World!", "test.txt")
  url <- publish("test.txt")
  expect_match(url, "test\\.txt$")

  # Test publish_data() with data frame
  df <- data.frame(x = 1:3, y = letters[1:3])
  url <- publish_data(df, "test-data.csv")
  expect_match(url, "test-data\\.csv$")

  # Test publish_list()
  files <- publish_list()
  expect_s3_class(files, "data.frame")
  expect_true(nrow(files) >= 2)

  # Test publish_dir()
  dir.create("test_dir")
  writeLines("file1", "test_dir/file1.txt")
  writeLines("file2", "test_dir/file2.txt")
  urls <- publish_dir("test_dir")
  expect_length(urls, 2)
})

Try the framework package in your browser

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

framework documentation built on Feb. 18, 2026, 1:07 a.m.