diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..4a0af40 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,12 @@ +^scripts$ +^Makefile$ +^README\.Rmd$ +^\.travis\.yml$ +^docs$ +^docker$ +^\.lintr$ +^\.hadolint\.yaml$ +^scripts$ +^.*\.Rproj$ +^\.Rproj\.user$ +^\.github$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..a3ac618 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..ed7650c --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,48 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.4.1 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..2c5bb50 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: test-coverage + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6926d34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.Rhistory +.RData +.Rproj.user +docs diff --git a/.lintr b/.lintr new file mode 100644 index 0000000..40c0755 --- /dev/null +++ b/.lintr @@ -0,0 +1,4 @@ +linters: with_defaults( + cyclocomp_linter = NULL + ) +exclusions: list("tests/testthat.R") diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..578b6e7 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,26 @@ +Package: orderly.sharedrive +Title: Shared drive remote for Orderly +Version: 0.1.5 +Authors@R: c(person("Rob", "Ashton", role = c("aut", "cre"), + email = "r.ashton@imperial.ac.uk"), + person("Imperial College of Science, Technology and Medicine", + role = "cph")) +Description: Store orderly reports on a shared drive. Provides an orderly + remote driver that can be used to share orderly reports using + a network drive or synced one drive. You can use this as a + a lightweight way of using orderly within a team. +License: MIT + file LICENSE +Encoding: UTF-8 +LazyData: true +Language: en-GB +Imports: + orderly1 (>= 1.7.0), + spud (>= 0.1.5), + zip +Suggests: + mockery, + testthat +Remotes: + reside-ic/spud, + orderly1=vimc/orderly@vimc-7135 +RoxygenNote: 7.0.2 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba7f83e --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2020 +COPYRIGHT HOLDER: Imperial College of Science, Technology and Medicine diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5117a17 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +PACKAGE := $(shell grep '^Package:' DESCRIPTION | sed -E 's/^Package:[[:space:]]+//') +RSCRIPT = Rscript --no-init-file + +all: install + +test: + ${RSCRIPT} -e 'library(methods); devtools::test()' + +test_all: + REMAKE_TEST_INSTALL_PACKAGES=true make test + +# https://github.com/r-lib/roxygen2/issues/963 +roxygen: + @mkdir -p man + ${RSCRIPT} -e "library(methods); devtools::document()" + +install: + R CMD INSTALL . + +build: + R CMD build . + +check: + _R_CHECK_CRAN_INCOMING_=FALSE make check_all + +check_all: + ${RSCRIPT} -e "rcmdcheck::rcmdcheck(args = c('--as-cran', '--no-manual'))" + +README.md: README.Rmd + Rscript -e "options(warnPartialMatchArgs=FALSE); knitr::knit('$<')" + sed -i.bak 's/[[:space:]]*$$//' README.md + rm -f $@.bak + + +pkgdown: + ${RSCRIPT} -e "library(methods); pkgdown::build_site()" + +website: pkgdown + ./scripts/update_web.sh + +vignettes: + ${RSCRIPT} -e 'tools::buildVignettes(dir = ".")' + mkdir -p inst/doc + cp vignettes/*.html vignettes/*.Rmd inst/doc + +.PHONY: all test document install vignettes diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..d95f9a9 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,3 @@ +# Generated by roxygen2: do not edit by hand + +export(orderly_remote_sharepoint) diff --git a/R/orderly.R b/R/orderly.R new file mode 100644 index 0000000..060f761 --- /dev/null +++ b/R/orderly.R @@ -0,0 +1,157 @@ +##' Implements an orderly "remote" using Sharepoint as a backend. Use +##' this within an \code{orderly_config.yml} configuration. +##' +##' A configuration might look like: +##' +##' \preformatted{ +##' remote: +##' real: +##' driver: orderly.sharepoint::orderly_remote_sharepoint +##' args: +##' url: https://example.sharepoint.com +##' site: mysite +##' path: Shared Documents/orderly/real +##' } +##' +##' which would create a remote called \code{real}, using your group's +##' Sharepoint hosted at \code{https://example.sharepoint.com}, on +##' site \code{mysite} and within that site using path \code{Shared +##' Documents/orderly/real}. +##' +##' Currently authentication is interactive, or uses the values of +##' environment variables \code{SHAREPOINT_USERNAME} and +##' \code{SHAREPOINT_PASS}. Once we expose richer authentication +##' approaches in spud that will be exposed here (RESIDE-162). +##' +##' This function is not intended to be used interactively +##' +##' @title Create an orderly remote based on Sharepoint +##' +##' @param url Sharepoint URL +##' +##' @param site Sharepoint "site" +##' +##' @param path Path within the Sharepoint site. In our experience +##' these often start with \code{Shared Documents} but your setup +##' may vary. +##' +##' @param name Friendly name for the remote +##' +##' @return An \code{orderly_remote_sharepoint} object +##' @return An \code{orderly_remote_sharepoint} object, designed to be +##' used by orderly. This function should however not generally be +##' called by users directly, as it should be used within +##' \code{orderly_config.yml} +##' @export +orderly_remote_sharepoint <- function(url, site, path, name = NULL) { + client <- orderly_sharepoint_client(url) + folder <- orderly_sharepoint_folder(client, site, path) + orderly_remote_sharepoint_$new(folder, name) +} + + +## Seems hard to mock the whole class out, which I think validates my +## general approach of exposing free constructor! +## https://github.com/r-lib/mockery/issues/21 +orderly_sharepoint_client <- function(url) { + spud::sharepoint$new(url) # nocov +} + + +orderly_sharepoint_folder <- function(client, site, path) { + folder <- tryCatch( + client$folder(site, path, verify = TRUE), + error = function(e) + stop(sprintf("Error reading from %s:%s - %s", + site, path, e$message), call. = FALSE)) + path <- "orderly.sharepoint" + exists <- tryCatch({ + folder$download(path) + TRUE + }, error = function(e) FALSE) + if (exists) { + return(folder) + } + if (nrow(folder$list()) > 0L) { + stop(sprintf( + "Directory %s:%s cannot be used for orderly; contains other files", + site, path)) + } + tmp <- tempfile() + on.exit(unlink(tmp)) + writeLines("orderly.sharepoint", tmp) + folder$upload(tmp, path) + folder$create("archive") + folder +} + + +orderly_remote_sharepoint_ <- R6::R6Class( + "orderly_remote_sharepoint", + cloneable = FALSE, + + public = list( + folder = NULL, + name = NULL, + + initialize = function(folder, name = NULL) { + self$folder <- folder + self$name <- name + }, + + list_reports = function() { + sort(self$folder$folders("archive")$name) + }, + + list_versions = function(name) { + sort(self$folder$files(file.path("archive", name))$name) + }, + + push = function(path) { + path_meta <- file.path(path, "orderly_run.rds") + stopifnot(file.exists(path_meta)) + + zip <- tempfile(fileext = ".zip") + zip_dir(path, zip) + on.exit(unlink(zip)) + + dat <- readRDS(path_meta) + name <- dat$meta$name + id <- dat$meta$id + + self$folder$create(file.path("archive", name)) + self$folder$upload(zip, file.path("archive", name, id)) + }, + + pull = function(name, id) { + zip <- tempfile(fileext = ".zip") + on.exit(unlink(zip)) + zip <- self$folder$download(file.path("archive", name, id), zip) + unzip_archive(zip, name, id) + }, + + metadata = function(name, id) { + archive_path <- self$pull(name, id) + file.path(archive_path, "orderly_run.rds") + }, + + run = function(...) { + stop("'orderly_remote_sharepoint' remotes do not run") + }, + + kill = function(...) { + stop("'orderly_remote_sharepoint' remotes do not support kill") + }, + + url_report = function(name, id) { + stop("'orderly_remote_sharepoint' remotes do not support urls") + }, + + bundle_pack = function(...) { + stop("'orderly_remote_sharepoint' remotes do not support bundles") + }, + + bundle_import = function(...) { + stop("'orderly_remote_sharepoint' remotes do not support bundles") + } + )) diff --git a/R/tools.R b/R/tools.R new file mode 100644 index 0000000..01720e6 --- /dev/null +++ b/R/tools.R @@ -0,0 +1,26 @@ +## NOTE: duplicated out of orderlyweb for now - it's not clear where +## this really belongs. See VIMC-3771 +unzip_archive <- function(zip, name, id) { + dest <- tempfile() + res <- utils::unzip(zip, exdir = dest) + + files <- dir(dest, all.files = TRUE, no.. = TRUE) + if (length(files) == 0L) { + stop("Corrupt zip file? No files extracted") + } else if (length(files) > 1L) { + stop("Invalid orderly archive", call. = FALSE) + } + if (files != id) { + stop(sprintf("This is archive '%s' but expected '%s'", + files, id), call. = FALSE) + } + + expected <- c("orderly.yml", "orderly_run.rds") + msg <- !file.exists(file.path(dest, id, expected)) + if (any(msg)) { + stop(sprintf("Invalid orderly archive: missing files %s", + paste(expected[msg], collapse = ", ")), call. = FALSE) + } + + file.path(dest, id) +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..08072d0 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,18 @@ +`%||%` <- function(x, y) { + if (is.null(x)) y else x +} + + +with_dir <- function(path, expr) { + owd <- setwd(path) + on.exit(setwd(owd)) + force(expr) +} + + +zip_dir <- function(path, dest = paste0(basename(path), ".zip")) { + with_dir(dirname(path), { + zip::zipr(dest, basename(path)) + normalizePath(dest) + }) +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f507125 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +## orderly.sharedrive + + +[![Project Status: Concept – Minimal or no implementation has been done yet, or the repository is only intended to be a limited example, demo, or proof-of-concept.](https://www.repostatus.org/badges/latest/concept.svg)](https://www.repostatus.org/#concept) +[![codecov.io](https://codecov.io/github/vimc/orderly.sharedrive/coverage.svg?branch=master)](https://codecov.io/github/vimc/orderly.sharedrive?branch=master) +[![R-CMD-check](https://github.com/vimc/orderly.sharedrive/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/vimc/orderly.sharedrive/actions/workflows/R-CMD-check.yaml) + + +An [`orderly1`](https://github.com/vimc/orderly1) remote hosted on a shared drive. Either a network share or a synced onedrive. + +### Usage + +Configure your `orderly_config.yml` as, for example: + +``` +remote: + real: + driver: orderly.sharedrive::orderly_remote_sharedrive + args: + path: ~/path/to/drive +``` + +Where + +* `path` is the path to your shared drive. This can be an environment variable + +`orderly.sharedrive` will store files as `archive//` where `` is the report name and `` is a zip archive of the report contents. These must be treated as read-only and must not be modified (they do not have a file extension to help this). + +With this set up, then `orderly1::pull_dependencies`, `orderly1::pull_archive` and `orderly1::push_archive` will work, and you can use your network or onedrive remote to distribute orderly results within your group. + +## License + +MIT © Imperial College of Science, Technology and Medicine diff --git a/inst/WORDLIST b/inst/WORDLIST new file mode 100644 index 0000000..10f555a --- /dev/null +++ b/inst/WORDLIST @@ -0,0 +1,5 @@ +Sharepoint +Sharepoint's +codecov +io +sharepoint diff --git a/man/orderly_remote_sharepoint.Rd b/man/orderly_remote_sharepoint.Rd new file mode 100644 index 0000000..585dfc6 --- /dev/null +++ b/man/orderly_remote_sharepoint.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/orderly.R +\name{orderly_remote_sharepoint} +\alias{orderly_remote_sharepoint} +\title{Create an orderly remote based on Sharepoint} +\usage{ +orderly_remote_sharepoint(url, site, path, name = NULL) +} +\arguments{ +\item{url}{Sharepoint URL} + +\item{site}{Sharepoint "site"} + +\item{path}{Path within the Sharepoint site. In our experience +these often start with \code{Shared Documents} but your setup +may vary.} + +\item{name}{Friendly name for the remote} +} +\value{ +An \code{orderly_remote_sharepoint} object + +An \code{orderly_remote_sharepoint} object, designed to be + used by orderly. This function should however not generally be + called by users directly, as it should be used within + \code{orderly_config.yml} +} +\description{ +Implements an orderly "remote" using Sharepoint as a backend. Use +this within an \code{orderly_config.yml} configuration. +} +\details{ +A configuration might look like: + +\preformatted{ +remote: + real: + driver: orderly.sharepoint::orderly_remote_sharepoint + args: + url: https://example.sharepoint.com + site: mysite + path: Shared Documents/orderly/real +} + +which would create a remote called \code{real}, using your group's +Sharepoint hosted at \code{https://example.sharepoint.com}, on +site \code{mysite} and within that site using path \code{Shared +Documents/orderly/real}. + +Currently authentication is interactive, or uses the values of +environment variables \code{SHAREPOINT_USERNAME} and +\code{SHAREPOINT_PASS}. Once we expose richer authentication +approaches in spud that will be exposed here (RESIDE-162). + +This function is not intended to be used interactively +} diff --git a/orderly.sharedrive.Rproj b/orderly.sharedrive.Rproj new file mode 100644 index 0000000..497f8bf --- /dev/null +++ b/orderly.sharedrive.Rproj @@ -0,0 +1,20 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source diff --git a/scripts/update_web.sh b/scripts/update_web.sh new file mode 100755 index 0000000..ce504c3 --- /dev/null +++ b/scripts/update_web.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +DOCS_DIR=docs +VERSION=$(git rev-parse --short HEAD) +REMOTE_URL=$(git config --get remote.origin.url) + +mkdir -p ${DOCS_DIR} +rm -rf ${DOCS_DIR}/.git +git init ${DOCS_DIR} +git -C ${DOCS_DIR} checkout --orphan gh-pages +git -C ${DOCS_DIR} add . +git -C ${DOCS_DIR} commit --no-verify -m "Update docs for version ${VERSION}" +git -C ${DOCS_DIR} remote add origin -m "gh-pages" ${REMOTE_URL} +git -C ${DOCS_DIR} push --force -u origin gh-pages diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..3724441 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(orderly.sharedrive) + +test_check("orderly.sharedrive") diff --git a/tests/testthat/test-orderly.R b/tests/testthat/test-orderly.R new file mode 100644 index 0000000..1f8f87a --- /dev/null +++ b/tests/testthat/test-orderly.R @@ -0,0 +1,235 @@ +context("orderly") + +test_that("list_reports calls folder$folders('archive')", { + folder <- list(folders = mockery::mock(list(name = c("a", "b", "c")))) + cl <- orderly_remote_sharepoint_$new(folder) + expect_equal(cl$list_reports(), c("a", "b", "c")) + mockery::expect_called(folder$folders, 1) + expect_equal(mockery::mock_args(folder$folders)[[1]], list("archive")) +}) + + +test_that("list_versions calls folder$files('archive/')", { + folder <- list(files = mockery::mock(list(name = c("a", "b", "c")))) + cl <- orderly_remote_sharepoint_$new(folder) + expect_equal(cl$list_versions("x"), c("a", "b", "c")) + mockery::expect_called(folder$files, 1) + expect_equal(mockery::mock_args(folder$files)[[1]], list("archive/x")) +}) + + +test_that("pull", { + path <- orderly1::orderly_example("minimal") + id <- orderly1::orderly_run("example", root = path, echo = FALSE) + p <- orderly1::orderly_commit(id, root = path) + zip <- zip_dir(p) + + folder <- list(download = mockery::mock(zip)) + cl <- orderly_remote_sharepoint_$new(folder) + res <- cl$pull("example", id) + + expect_true(file.exists(res)) + expect_true(file.info(res)$isdir) + expect_setequal(dir(res), dir(p)) + + mockery::expect_called(folder$download, 1) + args <- mockery::mock_args(folder$download)[[1]] + expect_equal(args[[1]], file.path("archive/example", id)) + expect_match(args[[2]], "\\.zip$") + expect_equal(normalizePath(dirname(args[[2]]), mustWork = TRUE), + normalizePath(tempdir(), mustWork = TRUE)) +}) + +test_that("metadata", { + path <- orderly1::orderly_example("minimal") + id <- orderly1::orderly_run("example", root = path, echo = FALSE) + p <- orderly1::orderly_commit(id, root = path) + zip <- zip_dir(p) + + folder <- list(download = mockery::mock(zip)) + cl <- orderly_remote_sharepoint_$new(folder) + res <- cl$metadata("example", id) + + expect_true(file.exists(res)) + ## metadata can be read + rds <- readRDS(res) + expect_true(!is.null(rds)) + + mockery::expect_called(folder$download, 1) + args <- mockery::mock_args(folder$download)[[1]] + expect_equal(args[[1]], file.path("archive/example", id)) + expect_match(args[[2]], "\\.zip$") + expect_equal(normalizePath(dirname(args[[2]]), mustWork = TRUE), + normalizePath(tempdir(), mustWork = TRUE)) +}) + + +test_that("push", { + path <- orderly1::orderly_example("minimal") + id <- orderly1::orderly_run("example", root = path, echo = FALSE) + p <- orderly1::orderly_commit(id, root = path) + + folder <- list(create = mockery::mock(), upload = mockery::mock()) + + cl <- orderly_remote_sharepoint_$new(folder) + + mock_zip <- mockery::mock(NULL) + mockery::stub(cl$push, "zip_dir", mock_zip) + res <- cl$push(p) + + mockery::expect_called(mock_zip, 1) + args <- mockery::mock_args(mock_zip)[[1]] + expect_equal(args[[1]], p) + expect_match(args[[2]], "\\.zip$") + zip <- args[[2]] + + mockery::expect_called(folder$create, 1) + expect_equal( + mockery::mock_args(folder$create)[[1]], + list("archive/example")) + mockery::expect_called(folder$upload, 1) + expect_equal( + mockery::mock_args(folder$upload)[[1]], + list(zip, file.path("archive/example", id))) +}) + + +test_that("report_run is not supported", { + cl <- orderly_remote_sharepoint_$new(NULL) + expect_error(cl$run(), + "'orderly_remote_sharepoint' remotes do not run") +}) + + +test_that("report_run is not supported", { + cl <- orderly_remote_sharepoint_$new(NULL) + expect_error(cl$kill("my_key"), + "'orderly_remote_sharepoint' remotes do not support kill") +}) + + +test_that("url_report is not supported", { + cl <- orderly_remote_sharepoint_$new(NULL) + expect_error(cl$url_report("a", "b"), + "'orderly_remote_sharepoint' remotes do not support urls") +}) + +test_that("bundles are not supported", { + cl <- orderly_remote_sharepoint_$new(NULL) + expect_error(cl$bundle_pack(), + "'orderly_remote_sharepoint' remotes do not support bundles") + expect_error(cl$bundle_import(), + "'orderly_remote_sharepoint' remotes do not support bundles") +}) + + +test_that("verify path on creation", { + client <- list( + folder = mockery::mock(stop("some error"))) + expect_error( + orderly_sharepoint_folder(client, "site", "path"), + "Error reading from site:path - some error") + + mockery::expect_called(client$folder, 1) + expect_equal(mockery::mock_args(client$folder)[[1]], + list("site", "path", verify = TRUE)) +}) + + +test_that("skip if already created", { + folder <- list(download = mockery::mock("orderly.sharepoint")) + client <- list(folder = mockery::mock(folder)) + + res <- orderly_sharepoint_folder(client, "site", "path") + expect_identical(res, folder) + + mockery::expect_called(client$folder, 1) + expect_equal(mockery::mock_args(client$folder)[[1]], + list("site", "path", verify = TRUE)) + + mockery::expect_called(folder$download, 1) + expect_equal(mockery::mock_args(folder$download)[[1]], + list("orderly.sharepoint")) +}) + + +test_that("continue if not created", { + folder <- list(download = mockery::mock(stop("not found")), + list = mockery::mock(data.frame(name = character(0))), + create = mockery::mock(NULL), + upload = mockery::mock(NULL)) + client <- list(folder = mockery::mock(folder)) + + res <- orderly_sharepoint_folder(client, "site", "path") + expect_identical(res, folder) + + mockery::expect_called(client$folder, 1) + expect_equal(mockery::mock_args(client$folder)[[1]], + list("site", "path", verify = TRUE)) + + mockery::expect_called(folder$download, 1) + expect_equal(mockery::mock_args(folder$download)[[1]], + list("orderly.sharepoint")) + + mockery::expect_called(folder$list, 1) + expect_equal(mockery::mock_args(folder$list)[[1]], list()) + + mockery::expect_called(folder$upload, 1) + args <- mockery::mock_args(folder$upload)[[1]] + expect_equal(args[[2]], "orderly.sharepoint") + + mockery::expect_called(folder$create, 1) + expect_equal(mockery::mock_args(folder$create)[[1]], + list("archive")) +}) + + +test_that("error if files exist", { + folder <- list(download = mockery::mock(stop("not found")), + list = mockery::mock(data.frame(name = "a")), + upload = mockery::mock(NULL)) + client <- list(folder = mockery::mock(folder)) + + expect_error( + orderly_sharepoint_folder(client, "site", "path"), + paste("Directory site:orderly.sharepoint cannot be used for orderly;", + "contains other files")) + + mockery::expect_called(client$folder, 1) + expect_equal(mockery::mock_args(client$folder)[[1]], + list("site", "path", verify = TRUE)) + + mockery::expect_called(folder$download, 1) + expect_equal(mockery::mock_args(folder$download)[[1]], + list("orderly.sharepoint")) + + mockery::expect_called(folder$list, 1) + expect_equal(mockery::mock_args(folder$list)[[1]], list()) + + mockery::expect_called(folder$upload, 0) +}) + + +test_that("creation", { + folder <- new.env() + mock_folder <- mockery::mock(folder) + client <- new.env() + mock_client <- mockery::mock(client) + + mockery::stub(orderly_remote_sharepoint, "orderly_sharepoint_client", + mock_client) + mockery::stub(orderly_remote_sharepoint, "orderly_sharepoint_folder", + mock_folder) + res <- orderly_remote_sharepoint("https://example.com", "site", "path", + name = "name") + expect_identical(res$folder, folder) + expect_identical(res$name, "name") + + mockery::expect_called(mock_client, 1) + expect_equal(mockery::mock_args(mock_client)[[1]], + list("https://example.com")) + + mockery::expect_called(mock_folder, 1) + expect_identical(mockery::mock_args(mock_folder)[[1]], + list(client, "site", "path")) +}) diff --git a/tests/testthat/test-tools.R b/tests/testthat/test-tools.R new file mode 100644 index 0000000..bd15f95 --- /dev/null +++ b/tests/testthat/test-tools.R @@ -0,0 +1,67 @@ +context("tools") + +test_that("unpack archive", { + testthat::skip_on_cran() + path <- orderly1::orderly_example("minimal") + id <- orderly1::orderly_run("example", root = path, echo = FALSE) + p <- orderly1::orderly_commit(id, root = path) + + zip <- zip_dir(p) + + path <- unzip_archive(zip, "example", id) + expect_equal(basename(path), id) + expect_equal(sort(dir(file.path(path), recursive = TRUE)), + sort(dir(p, recursive = TRUE))) +}) + + +test_that("unpack report failure: corrupt download", { + testthat::skip_on_cran() + bytes <- as.raw(c(0x50, 0x4b, 0x05, 0x06, rep(0x00, 18L))) + zip <- tempfile() + writeBin(bytes, zip) + ## This test might be platform dependent as a sane unzip function + ## would have caught this. + expect_error(suppressWarnings( + unzip_archive(zip, NULL, NULL)), + "Corrupt zip file? No files extracted", + fixed = TRUE) +}) + + +test_that("unpack failure: not an orderly archive", { + testthat::skip_on_cran() + tmp <- file.path(tempfile(), "parent") + dir.create(tmp, FALSE, TRUE) + file.create(file.path(tmp, c("a", "b"))) + zip <- tempfile(fileext = ".zip") + with_dir(tmp, zip(zip, dir(), extras = "-q")) + expect_error(unzip_archive(zip, NULL, NULL), + "Invalid orderly archive") +}) + + +test_that("unpack failure: not expected id", { + testthat::skip_on_cran() + id <- orderly1:::new_report_id() + tmp <- file.path(tempfile(), id) + dir.create(tmp, FALSE, TRUE) + dir.create(file.path(tmp, "orderly.yml")) + zip <- zip_dir(tmp) + expect_error(unzip_archive(zip, NULL, "other"), + sprintf("This is archive '%s' but expected 'other'", id), + fixed = TRUE) +}) + + +test_that("unpack failure: missing files", { + testthat::skip_on_cran() + id <- orderly1:::new_report_id() + tmp <- file.path(tempfile(), id) + dir.create(tmp, FALSE, TRUE) + dir.create(file.path(tmp, "orderly.yml")) + zip <- zip_dir(tmp) + expect_error(unzip_archive(zip, NULL, id), + "Invalid orderly archive: missing files orderly_run.rds", + fixed = TRUE) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R new file mode 100644 index 0000000..bfa22d0 --- /dev/null +++ b/tests/testthat/test-utils.R @@ -0,0 +1,8 @@ +context("utils") + +test_that("null-or-value works", { + expect_equal(1 %||% NULL, 1) + expect_equal(1 %||% 2, 1) + expect_equal(NULL %||% NULL, NULL) + expect_equal(NULL %||% 2, 2) +})