From f69cf62b2771d5b6ed605c04b7ddd618f5a272c2 Mon Sep 17 00:00:00 2001 From: Norwin Date: Thu, 16 May 2019 12:35:52 +0200 Subject: [PATCH] fail gracefully when api is not available (#27) more robust error handling when API not available (#26) --- R/api.R | 28 ++++++++++++++++++++++++++-- tests/testthat/test_api.R | 7 +++++++ tests/testthat/test_archive.R | 4 ++++ tests/testthat/test_box.R | 8 ++++++++ tests/testthat/test_measurements.R | 1 + tests/testthat/test_phenomena.R | 2 ++ 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test_api.R diff --git a/R/api.R b/R/api.R index 85fe61f..273c7f2 100644 --- a/R/api.R +++ b/R/api.R @@ -4,11 +4,32 @@ # for CSV responses (get_measurements) the readr package is a hidden dependency # ============================================================================== +default_api = 'https://api.opensensemap.org' + #' Get the default openSenseMap API endpoint #' @export #' @return A character string with the HTTP URL of the openSenseMap API -osem_endpoint = function() { - 'https://api.opensensemap.org' +osem_endpoint = function() default_api + +#' Check if the given openSenseMap API endpoint is available +#' @param endpoint The API base URL to check, defaulting to \code{\link{osem_endpoint}} +#' @return \code{TRUE} if the API is available, otherwise \code{stop()} is called. +osem_ensure_api_available = function(endpoint = osem_endpoint()) { + code = FALSE + try({ + code = httr::status_code(httr::GET(endpoint, path='stats')) + }, silent = TRUE) + + if (code == 200) + return(TRUE) + + errtext = paste('The API at', endpoint, 'is currently not available.') + if (code != FALSE) + errtext = paste0(errtext, ' (HTTP code ', code, ')') + if (endpoint == default_api) + errtext = c(errtext, 'If the issue persists, please check back at https://status.sensebox.de/778247404 and notify support@sensebox.de') + stop(paste(errtext, collapse='\n '), call. = FALSE) + FALSE } get_boxes_ = function (..., endpoint) { @@ -115,6 +136,9 @@ osem_clear_cache = function (location = tempdir()) { } osem_request_ = function (host, path, query = list(), type = 'parsed', progress = TRUE) { + # stop() if API is not available + osem_ensure_api_available(host) + progress = if (progress && !is_non_interactive()) httr::progress() else NULL res = httr::GET(host, progress, path = path, query = query) diff --git a/tests/testthat/test_api.R b/tests/testthat/test_api.R new file mode 100644 index 0000000..db55594 --- /dev/null +++ b/tests/testthat/test_api.R @@ -0,0 +1,7 @@ +context('API error handling') + +test_that('unavailable API yields informative error message', { + expect_error({ + osem_boxes(endpoint = 'example.zip') + }, 'The API at example.zip is currently not available') +}) diff --git a/tests/testthat/test_archive.R b/tests/testthat/test_archive.R index 3449417..df5f204 100644 --- a/tests/testthat/test_archive.R +++ b/tests/testthat/test_archive.R @@ -18,6 +18,7 @@ test_that('osem_box_to_archive_name does the correct character replacements', { }) test_that('osem_box_to_archive_name works for one box', { + check_api() if (is.null(box)) skip('no box data could be fetched') archivename = opensensmapr:::osem_box_to_archivename(box) @@ -26,6 +27,7 @@ test_that('osem_box_to_archive_name works for one box', { }) test_that('osem_box_to_archive_name works for multiple boxes', { + check_api() if (is.null(boxes)) skip('no box data available') archivename = opensensmapr:::osem_box_to_archivename(boxes) @@ -36,6 +38,7 @@ test_that('osem_box_to_archive_name works for multiple boxes', { context('osem_measurements_archive()') test_that('osem_measurements_archive works for one box', { + check_api() if (is.null(box)) skip('no box data could be fetched') m = osem_measurements_archive(box, as.POSIXlt('2018-08-08')) @@ -44,6 +47,7 @@ test_that('osem_measurements_archive works for one box', { }) test_that('osem_measurements_archive fails for multiple boxes', { + check_api() if (is.null(boxes)) skip('no box data available') expect_error( diff --git a/tests/testthat/test_box.R b/tests/testthat/test_box.R index 3e1b384..d6d7712 100644 --- a/tests/testthat/test_box.R +++ b/tests/testthat/test_box.R @@ -19,6 +19,8 @@ test_that('a single box can be retrieved by ID', { }) test_that('required box attributes are correctly parsed', { + check_api() + expect_is(box$X_id, 'character') expect_is(box$name, 'character') expect_is(box$exposure, 'character') @@ -29,6 +31,8 @@ test_that('required box attributes are correctly parsed', { }) test_that('optional box attributes are correctly parsed', { + check_api() + completebox = osem_box('5a676e49411a790019290f94') # all fields populated expect_is(completebox$description, 'character') expect_is(completebox$grouptag, 'character') @@ -65,6 +69,8 @@ test_that('[.sensebox maintains attributes', { }) test_that("print.sensebox filters important attributes for a single box", { + check_api() + msg = capture.output({ print(box) }) @@ -72,6 +78,8 @@ test_that("print.sensebox filters important attributes for a single box", { }) test_that("summary.sensebox outputs all metrics for a single box", { + check_api() + msg = capture.output({ summary(box) }) diff --git a/tests/testthat/test_measurements.R b/tests/testthat/test_measurements.R index 4d894f1..4dca599 100644 --- a/tests/testthat/test_measurements.R +++ b/tests/testthat/test_measurements.R @@ -123,6 +123,7 @@ test_that('[.osem_measurements maintains attributes', { }) test_that('data.frame can be converted to measurements data.frame', { + check_api() m = osem_measurements('Windrichtung') df = osem_as_measurements(data.frame(c(1, 2), c('a', 'b'))) expect_equal(class(df), class(m)) diff --git a/tests/testthat/test_phenomena.R b/tests/testthat/test_phenomena.R index 557b982..b442e62 100644 --- a/tests/testthat/test_phenomena.R +++ b/tests/testthat/test_phenomena.R @@ -25,6 +25,8 @@ test_that('phenomena from boxes has all phenomena', { }) test_that('phenomena from a not sensebox data.frame returns error', { + check_api() + expect_error(osem_phenomena(list()), 'no applicable method') expect_error(osem_phenomena(data.frame()), 'no applicable method') boxes_df = boxes