From 4d47320e7601fb0916231d4f5ee8f4f8d589f657 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Fri, 9 Jan 2026 17:16:08 +0100 Subject: [PATCH 1/6] Swith to {httr2} to download data and add update tests accordingly using {vcr} --- DESCRIPTION | 5 +- R/download_data.R | 115 ++++-- tests/testthat/Rplots.pdf | Bin 42019 -> 42019 bytes tests/testthat/_vcr/character_dates.yml | 20 + .../testthat/_vcr/get_multiple_parameters.yml | 38 ++ tests/testthat/_vcr/get_single_parameter.yml | 20 + tests/testthat/_vcr/numeric_dates.yml | 20 + tests/testthat/_vcr/week_of_data.yml | 38 ++ tests/testthat/test-data_download.R | 32 -- tests/testthat/tests_download_parameters.R | 349 ++++++++++++++++++ 10 files changed, 576 insertions(+), 61 deletions(-) create mode 100644 tests/testthat/_vcr/character_dates.yml create mode 100644 tests/testthat/_vcr/get_multiple_parameters.yml create mode 100644 tests/testthat/_vcr/get_single_parameter.yml create mode 100644 tests/testthat/_vcr/numeric_dates.yml create mode 100644 tests/testthat/_vcr/week_of_data.yml create mode 100644 tests/testthat/tests_download_parameters.R diff --git a/DESCRIPTION b/DESCRIPTION index e4b823b..815fd1d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,7 +25,7 @@ Imports: ggplot2, grid, gridtext, - jsonlite, + httr2, lubridate, ncdf4, patchwork, @@ -41,7 +41,8 @@ Suggests: knitr, mockery, rmarkdown, - testthat + testthat, + vcr LinkingTo: Rcpp, RcppArmadillo diff --git a/R/download_data.R b/R/download_data.R index 01615fc..9fad88e 100644 --- a/R/download_data.R +++ b/R/download_data.R @@ -11,10 +11,11 @@ #' @return a tibble with 2 columns and as many rows as needed #' @noRd get_parameters_raw <- function( - parameter = "hs", - node = 42, - start = as.POSIXct("1994-01-01Z00:00:00", tz = "UTC"), - end = as.POSIXct("1994-12-31Z23:00:00", tz = "UTC")) { + parameter = "hs", + node = 42, + start = as.POSIXct("1994-01-01Z00:00:00", tz = "UTC"), + end = as.POSIXct("1994-12-31Z23:00:00", tz = "UTC") +) { if (parameter == "tp") { single_parameter <- "fp" } else { @@ -27,7 +28,7 @@ get_parameters_raw <- function( # Cassandra database start indexing at 1, so decrements node number node <- node - 1 - request <- paste0( + request_url <- paste0( rcd_cassandra_url, "api/timeseries", "?parameter=", @@ -40,28 +41,68 @@ get_parameters_raw <- function( end_str ) - # Try retrieving and parsing JSON + # Try retrieving and parsing JSON using httr2 + resp <- tryCatch( + httr2::request(request_url) |> + httr2::req_error(is_error = \(resp) FALSE) |> # Don't auto-error on HTTP errors + httr2::req_retry(max_tries = 3) |> # Retry transient failures + httr2::req_timeout(30) |> # 30 second timeout + httr2::req_perform(), + httr2_failure = function(cnd) { + message( + "Network error: Could not connect to the remote resource. ", + "The server may be unavailable." + ) + NULL + }, + error = function(e) { + message("Unexpected error retrieving data: ", conditionMessage(e)) + NULL + } + ) + + # If request failed, exit + if (is.null(resp)) { + return(NULL) + } + + # Check HTTP status + if (httr2::resp_status(resp) != 200) { + message( + "HTTP error ", + httr2::resp_status(resp), + ": ", + httr2::resp_status_desc(resp) + ) + return(NULL) + } + + # Parse JSON response res <- tryCatch( - jsonlite::fromJSON(request), + httr2::resp_body_json(resp, simplifyVector = TRUE), error = function(e) { - message("Could not retrieve data from the remote resource. ", - "The server may be unavailable or the URL may have changed.") - NULL # graceful fallback + message("Error parsing response: Invalid JSON format") + NULL } ) - # If retrieval failed, exit - if (is.null(res)) return(NULL) + # If parsing failed, exit + if (is.null(res)) { + return(NULL) + } # Check API-level error if (!is.null(res$errorcode) && res$errorcode != 0) { - message("The data source returned an error: ", res$errormessage, - "\nReturning NULL.") - return(NULL) # graceful fallback + message( + "The data source returned an error: ", + res$errormessage, + "\nReturning NULL." + ) + return(NULL) # graceful fallback } - - data <- res$result$data + # Convert list to data frame + data <- as.data.frame(res$result$data) colnames(data) <- c("time", parameter) data <- tibble::as_tibble(data) @@ -70,7 +111,7 @@ get_parameters_raw <- function( } data$time <- as.POSIXct( - data$time / 1000, + as.numeric(data$time) / 1000, origin = as.POSIXct("1970-01-01", tz = "UTC"), tz = "UTC" ) # Convert UNIX time (ms) to POSIXct format @@ -78,6 +119,7 @@ get_parameters_raw <- function( data } + #' Download time series of sea-state parameters from RESOURCECODE database #' #' If the remote resource is unavailable or returns an error, the function returns NULL @@ -95,17 +137,21 @@ get_parameters_raw <- function( #' ts <- get_parameters(parameters = c("hs", "tp"), node = 42) #' plot(ts$time, ts$hs, type = "l") get_parameters <- function( - parameters = "hs", - node = 42, - start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), - end = as.POSIXct("1994-12-31 23:00:00", tz = "UTC")) { + parameters = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-12-31 23:00:00", tz = "UTC") +) { parameters <- tolower(parameters) if (any(parameters %nin% c("tp", resourcecodedata::rscd_variables$name))) { - errors <- parameters[parameters %nin% c("tp", resourcecodedata::rscd_variables$name)] + errors <- parameters[ + parameters %nin% c("tp", resourcecodedata::rscd_variables$name) + ] stop( "Requested parameters do not exists in the database: ", - paste0(errors, collapse = ", "), "." + paste0(errors, collapse = ", "), + "." ) } @@ -144,7 +190,8 @@ get_parameters <- function( stop( "'start' is outside the covered period: ", paste( - format(c(rscd_casandra_start_date, rscd_casandra_end_date), + format( + c(rscd_casandra_start_date, rscd_casandra_end_date), format = "%Y-%m-%d %H:%M %Z" ), collapse = " \u2014 " @@ -155,7 +202,8 @@ get_parameters <- function( stop( "'end' is outside the covered period: ", paste( - format(c(rscd_casandra_start_date, rscd_casandra_end_date), + format( + c(rscd_casandra_start_date, rscd_casandra_end_date), format = "%Y-%m-%d %H:%M %Z" ), collapse = " \u2014 " @@ -173,6 +221,12 @@ get_parameters <- function( end = end ) + # If first parameter retrieval failed, return NULL + if (is.null(out)) { + message("Failed to retrieve parameter: ", parameters[1]) + return(NULL) + } + for (i in seq_len(length(parameters) - 1)) { temp <- get_parameters_raw( parameters[i + 1], @@ -180,7 +234,14 @@ get_parameters <- function( start = start, end = end ) - out <- cbind(out, temp[, 2]) + + # If any subsequent parameter retrieval fails, return NULL + if (is.null(temp)) { + message("Failed to retrieve parameter: ", parameters[i + 1]) + return(NULL) + } + + out <- cbind.data.frame(out, temp[, 2]) } out } diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index ee6ecec07822c721b45918da5ae15a519747a3f1..fe295bc179876f0d3628091afdb7eeb6a8f1adc7 100644 GIT binary patch delta 27 fcmZ2{f@$#yrU_Q8=7z=wh7;|@A&iaDCl&($fHw&R delta 27 fcmZ2{f@$#yrU_Q8hDPRwCKK(&A&iaDCl&($fLI9& diff --git a/tests/testthat/_vcr/character_dates.yml b/tests/testthat/_vcr/character_dates.yml new file mode 100644 index 0000000..c30ba14 --- /dev/null +++ b/tests/testthat/_vcr/character_dates.yml @@ -0,0 +1,20 @@ +http_interactions: +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=hs&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' + recorded_at: 2026-01-09 16:05:07 +recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/get_multiple_parameters.yml b/tests/testthat/_vcr/get_multiple_parameters.yml new file mode 100644 index 0000000..2c31e02 --- /dev/null +++ b/tests/testthat/_vcr/get_multiple_parameters.yml @@ -0,0 +1,38 @@ +http_interactions: +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=hs&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' + recorded_at: 2026-01-09 16:05:07 +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=fp&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"fp","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.067],[757386000000,0.067],[757389600000,0.068],[757393200000,0.069],[757396800000,0.069],[757400400000,0.069],[757404000000,0.068],[757407600000,0.068],[757411200000,0.068],[757414800000,0.068],[757418400000,0.068],[757422000000,0.069],[757425600000,0.07],[757429200000,0.071],[757432800000,0.072],[757436400000,0.073],[757440000000,0.074],[757443600000,0.074],[757447200000,0.074],[757450800000,0.073],[757454400000,0.072],[757458000000,0.072],[757461600000,0.072],[757465200000,0.072],[757468800000,0.072]]}}' + recorded_at: 2026-01-09 16:05:07 +recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/get_single_parameter.yml b/tests/testthat/_vcr/get_single_parameter.yml new file mode 100644 index 0000000..9538244 --- /dev/null +++ b/tests/testthat/_vcr/get_single_parameter.yml @@ -0,0 +1,20 @@ +http_interactions: +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=hs&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:06 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' + recorded_at: 2026-01-09 16:05:07 +recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/numeric_dates.yml b/tests/testthat/_vcr/numeric_dates.yml new file mode 100644 index 0000000..c30ba14 --- /dev/null +++ b/tests/testthat/_vcr/numeric_dates.yml @@ -0,0 +1,20 @@ +http_interactions: +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=hs&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' + recorded_at: 2026-01-09 16:05:07 +recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/week_of_data.yml b/tests/testthat/_vcr/week_of_data.yml new file mode 100644 index 0000000..8d20410 --- /dev/null +++ b/tests/testthat/_vcr/week_of_data.yml @@ -0,0 +1,38 @@ +http_interactions: +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=hs&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-07T23%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757983600000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-07T23:00:00Z"},"result":{"dataSetSizeBeforeRegression":168,"dataSetSizeAfterRegression":168,"dataSetSize":168,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034],[757472400000,0.036],[757476000000,0.036],[757479600000,0.034],[757483200000,0.03],[757486800000,0.026],[757490400000,0.022],[757494000000,0.018],[757497600000,0.018],[757501200000,0.02],[757504800000,0.024],[757508400000,0.028],[757512000000,0.032],[757515600000,0.034],[757519200000,0.034],[757522800000,0.032],[757526400000,0.032],[757530000000,0.036],[757533600000,0.056],[757537200000,0.066],[757540800000,0.062],[757544400000,0.058],[757548000000,0.058],[757551600000,0.058],[757555200000,0.058],[757558800000,0.062],[757562400000,0.062],[757566000000,0.058],[757569600000,0.052],[757573200000,0.042],[757576800000,0.032],[757580400000,0.026],[757584000000,0.022],[757587600000,0.02],[757591200000,0.022],[757594800000,0.024],[757598400000,0.028],[757602000000,0.032],[757605600000,0.034],[757609200000,0.034],[757612800000,0.032],[757616400000,0.03],[757620000000,0.028],[757623600000,0.024],[757627200000,0.024],[757630800000,0.024],[757634400000,0.026],[757638000000,0.03],[757641600000,0.034],[757645200000,0.036],[757648800000,0.038],[757652400000,0.04],[757656000000,0.038],[757659600000,0.036],[757663200000,0.032],[757666800000,0.028],[757670400000,0.026],[757674000000,0.03],[757677600000,0.068],[757681200000,0.08],[757684800000,0.082],[757688400000,0.088],[757692000000,0.096],[757695600000,0.104],[757699200000,0.11],[757702800000,0.112],[757706400000,0.112],[757710000000,0.108],[757713600000,0.1],[757717200000,0.096],[757720800000,0.1],[757724400000,0.108],[757728000000,0.116],[757731600000,0.124],[757735200000,0.126],[757738800000,0.128],[757742400000,0.124],[757746000000,0.114],[757749600000,0.106],[757753200000,0.102],[757756800000,0.1],[757760400000,0.094],[757764000000,0.092],[757767600000,0.094],[757771200000,0.094],[757774800000,0.092],[757778400000,0.09],[757782000000,0.088],[757785600000,0.084],[757789200000,0.08],[757792800000,0.074],[757796400000,0.074],[757800000000,0.074],[757803600000,0.078],[757807200000,0.076],[757810800000,0.074],[757814400000,0.074],[757818000000,0.072],[757821600000,0.066],[757825200000,0.064],[757828800000,0.068],[757832400000,0.074],[757836000000,0.076],[757839600000,0.076],[757843200000,0.074],[757846800000,0.074],[757850400000,0.068],[757854000000,0.06],[757857600000,0.056],[757861200000,0.05],[757864800000,0.046],[757868400000,0.04],[757872000000,0.038],[757875600000,0.034],[757879200000,0.032],[757882800000,0.03],[757886400000,0.028],[757890000000,0.024],[757893600000,0.022],[757897200000,0.018],[757900800000,0.016],[757904400000,0.016],[757908000000,0.016],[757911600000,0.018],[757915200000,0.02],[757918800000,0.022],[757922400000,0.024],[757926000000,0.024],[757929600000,0.024],[757933200000,0.022],[757936800000,0.02],[757940400000,0.018],[757944000000,0.016],[757947600000,0.016],[757951200000,0.018],[757954800000,0.02],[757958400000,0.024],[757962000000,0.028],[757965600000,0.03],[757969200000,0.032],[757972800000,0.03],[757976400000,0.026],[757980000000,0.024],[757983600000,0.022]]}}' + recorded_at: 2026-01-09 16:05:07 +- request: + method: GET + uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=fp&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-07T23%3A00%3A00Z + response: + status: 200 + headers: + Date: Fri, 09 Jan 2026 16:05:07 GMT + Server: Microsoft-IIS/8.0 + Content-Type: application/json;charset=UTF-8 + Vary: Accept-Encoding + Content-Encoding: gzip + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin + Access-Control-Allow-Origin: '*' + Transfer-Encoding: chunked + body: + string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"fp","startTimeMillis":"757382400000","endTimeMillis":"757983600000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-07T23:00:00Z"},"result":{"dataSetSizeBeforeRegression":168,"dataSetSizeAfterRegression":168,"dataSetSize":168,"data":[[757382400000,0.067],[757386000000,0.067],[757389600000,0.068],[757393200000,0.069],[757396800000,0.069],[757400400000,0.069],[757404000000,0.068],[757407600000,0.068],[757411200000,0.068],[757414800000,0.068],[757418400000,0.068],[757422000000,0.069],[757425600000,0.07],[757429200000,0.071],[757432800000,0.072],[757436400000,0.073],[757440000000,0.074],[757443600000,0.074],[757447200000,0.074],[757450800000,0.073],[757454400000,0.072],[757458000000,0.072],[757461600000,0.072],[757465200000,0.072],[757468800000,0.072],[757472400000,0.072],[757476000000,0.072],[757479600000,0.072],[757483200000,0.071],[757486800000,0.069],[757490400000,0.069],[757494000000,0.069],[757497600000,0.069],[757501200000,0.07],[757504800000,0.071],[757508400000,0.072],[757512000000,0.073],[757515600000,0.074],[757519200000,0.074],[757522800000,0.075],[757526400000,0.077],[757530000000,0.079],[757533600000,0.08],[757537200000,0.862],[757540800000,0.821],[757544400000,0.796],[757548000000,0.799],[757551600000,0.081],[757555200000,0.081],[757558800000,0.081],[757562400000,0.082],[757566000000,0.082],[757569600000,0.083],[757573200000,0.084],[757576800000,0.085],[757580400000,0.085],[757584000000,0.085],[757587600000,0.046],[757591200000,0.047],[757594800000,0.049],[757598400000,0.049],[757602000000,0.05],[757605600000,0.05],[757609200000,0.051],[757612800000,0.052],[757616400000,0.053],[757620000000,0.054],[757623600000,0.055],[757627200000,0.056],[757630800000,0.056],[757634400000,0.057],[757638000000,0.059],[757641600000,0.059],[757645200000,0.06],[757648800000,0.06],[757652400000,0.061],[757656000000,0.061],[757659600000,0.062],[757663200000,0.062],[757666800000,0.062],[757670400000,0.062],[757674000000,0.062],[757677600000,0.063],[757681200000,0.064],[757684800000,0.065],[757688400000,0.066],[757692000000,0.066],[757695600000,0.067],[757699200000,0.652],[757702800000,0.643],[757706400000,0.639],[757710000000,0.647],[757713600000,0.655],[757717200000,0.662],[757720800000,0.66],[757724400000,0.646],[757728000000,0.616],[757731600000,0.605],[757735200000,0.6],[757738800000,0.597],[757742400000,0.599],[757746000000,0.607],[757749600000,0.62],[757753200000,0.651],[757756800000,0.661],[757760400000,0.665],[757764000000,0.669],[757767600000,0.668],[757771200000,0.664],[757774800000,0.664],[757778400000,0.667],[757782000000,0.671],[757785600000,0.676],[757789200000,0.688],[757792800000,0.709],[757796400000,0.725],[757800000000,0.729],[757803600000,0.729],[757807200000,0.722],[757810800000,0.723],[757814400000,0.722],[757818000000,0.715],[757821600000,0.713],[757825200000,0.721],[757828800000,0.734],[757832400000,0.733],[757836000000,0.728],[757839600000,0.724],[757843200000,0.724],[757846800000,0.726],[757850400000,0.728],[757854000000,0.733],[757857600000,0.74],[757861200000,0.755],[757864800000,0.775],[757868400000,0.783],[757872000000,0.09],[757875600000,0.09],[757879200000,0.072],[757882800000,0.072],[757886400000,0.073],[757890000000,0.073],[757893600000,0.074],[757897200000,0.074],[757900800000,0.075],[757904400000,0.075],[757908000000,0.075],[757911600000,0.074],[757915200000,0.07],[757918800000,0.069],[757922400000,0.069],[757926000000,0.069],[757929600000,0.071],[757933200000,0.072],[757936800000,0.073],[757940400000,0.073],[757944000000,0.073],[757947600000,0.073],[757951200000,0.073],[757954800000,0.073],[757958400000,0.073],[757962000000,0.073],[757965600000,0.074],[757969200000,0.075],[757972800000,0.076],[757976400000,0.077],[757980000000,0.077],[757983600000,0.078]]}}' + recorded_at: 2026-01-09 16:05:07 +recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/test-data_download.R b/tests/testthat/test-data_download.R index d3c588f..47f7dec 100644 --- a/tests/testthat/test-data_download.R +++ b/tests/testthat/test-data_download.R @@ -74,38 +74,6 @@ test_that("downloading parameters data works", { ) }) -test_that("get_parameters_raw() returns NULL and message on failure", { - get_parameters_raw <- getFromNamespace("get_parameters_raw", "resourcecode") - # mock a function that throws error (as if network/API failed) - mock_fromJSON <- function(...) stop("network failure") # nolint - - # temporarily replace fromJSON inside your function - mockery::stub(get_parameters_raw, "jsonlite::fromJSON", mock_fromJSON) - - expect_message( - result <- get_parameters_raw("hs"), - "Could not retrieve data" - ) - - expect_null(result) -}) - -test_that("get_parameters_raw() handles API-side error codes", { - get_parameters_raw <- getFromNamespace("get_parameters_raw", "resourcecode") - fake_api_response <- list(errorcode = 123, errormessage = "Invalid request") - - mock_fromJSON <- function(...) fake_api_response # nolint - - mockery::stub(get_parameters_raw, "jsonlite::fromJSON", mock_fromJSON) - - expect_message( - result <- get_parameters_raw("anything"), - "The data source returned an error" - ) - - expect_null(result) -}) - test_that("downloading 1D spectral data works", { skip_if_offline() spec <- get_1d_spectrum( diff --git a/tests/testthat/tests_download_parameters.R b/tests/testthat/tests_download_parameters.R new file mode 100644 index 0000000..c7481cb --- /dev/null +++ b/tests/testthat/tests_download_parameters.R @@ -0,0 +1,349 @@ +# Configure vcr for your tests +# vcr_configure( +# dir = "tests/fixtures/vcr_cassettes", +# filter_sensitive_data = list( +# # If you have API keys or sensitive data in URLs, filter them here +# # "<<>>" = Sys.getenv("SECRET_KEY") +# ) +# ) + +#Tests for get_parameters function (which also tests get_parameters_raw internally) +test_that("get_parameters retrieves single parameter and tests basic functionality", { + vcr::local_cassette("get_single_parameter") + result <- get_parameters( + parameters = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ) + + expect_s3_class(result, "data.frame") + expect_named(result, c("time", "hs")) + expect_s3_class(result$time, "POSIXct") + expect_type(result$hs, "double") + expect_true(nrow(result) > 0) +}) + +test_that("get_parameters retrieves multiple parameters including tp conversion", { + vcr::local_cassette("get_multiple_parameters") + result <- get_parameters( + parameters = c("hs", "tp"), + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ) + + expect_s3_class(result, "data.frame") + expect_named(result, c("time", "hs", "tp")) + expect_true(nrow(result) > 0) + # Check that tp values are positive (tests fp to tp conversion in get_parameters_raw) + expect_true(all(result$tp > 0, na.rm = TRUE)) +}) + +test_that("get_parameters handles character date inputs", { + vcr::local_cassette("character_dates") + result <- get_parameters( + parameters = "hs", + node = 42, + start = "1994-01-01 00:00:00", + end = "1994-01-02 00:00:00" + ) + + expect_s3_class(result, "data.frame") + expect_true(nrow(result) > 0) +}) + +test_that("get_parameters handles numeric date inputs", { + vcr::local_cassette("numeric_dates") + start_num <- as.numeric(as.POSIXct("1994-01-01 00:00:00", tz = "UTC")) + end_num <- as.numeric(as.POSIXct("1994-01-02 00:00:00", tz = "UTC")) + + result <- get_parameters( + parameters = "hs", + node = 42, + start = start_num, + end = end_num + ) + + expect_s3_class(result, "data.frame") + expect_true(nrow(result) > 0) +}) + +# Error handling tests (these don't need vcr as they fail before API call) +test_that("get_parameters validates parameter names", { + expect_error( + get_parameters( + parameters = c("hs", "invalid_param"), + node = 42 + ), + "Requested parameters do not exists" + ) +}) + +test_that("get_parameters validates node input", { + expect_error( + get_parameters( + parameters = "hs", + node = c(42, 43) + ), + "only one location a time" + ) +}) + +test_that("get_parameters validates date range", { + expect_error( + get_parameters( + parameters = "hs", + node = 42, + start = as.POSIXct("1994-01-02 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-01 00:00:00", tz = "UTC") + ), + "'end' must be after 'start'" + ) +}) + +# Test with recorded fixtures to ensure consistent behavior +test_that("get_parameters produces expected data structure over time range", { + vcr::local_cassette("week_of_data") + result <- get_parameters( + parameters = c("hs", "tp"), + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-07 23:00:00", tz = "UTC") + ) + + expect_s3_class(result, "data.frame") + expect_equal(ncol(result), 3) # time, hs, tp + # Expect roughly hourly data for a week (168 hours) + expect_gt(nrow(result), 100) + expect_lt(nrow(result), 200) + + # Check data types + expect_s3_class(result$time, "POSIXct") + expect_type(result$hs, "double") + expect_type(result$tp, "double") +}) + +# Error handling tests using mockery +# test_that("get_parameters_raw handles network timeout", { +# mockery::stub( +# get_parameters_raw, +# "httr2::req_perform", +# stop("Timeout was reached") +# ) +# +# expect_message( +# result <- get_parameters_raw( +# parameter = "hs", +# node = 42, +# start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), +# end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") +# ), +# "Network error" +# ) +# +# expect_null(result) +# }) + +test_that("get_parameters_raw handles HTTP 404 error", { + mock_resp <- structure( + list(status_code = 404), + class = "httr2_response" + ) + + mockery::stub(get_parameters_raw, "httr2::req_perform", mock_resp) + mockery::stub(get_parameters_raw, "httr2::resp_status", 404) + mockery::stub(get_parameters_raw, "httr2::resp_status_desc", "Not Found") + + expect_message( + result <- get_parameters_raw( + parameter = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "HTTP error 404" + ) + + expect_null(result) +}) + +test_that("get_parameters_raw handles HTTP 500 server error", { + mock_resp <- structure( + list(status_code = 500), + class = "httr2_response" + ) + + mockery::stub(get_parameters_raw, "httr2::req_perform", mock_resp) + mockery::stub(get_parameters_raw, "httr2::resp_status", 500) + mockery::stub( + get_parameters_raw, + "httr2::resp_status_desc", + "Internal Server Error" + ) + + expect_message( + result <- get_parameters_raw( + parameter = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "HTTP error 500" + ) + + expect_null(result) +}) + +# test_that("get_parameters_raw handles invalid JSON response", { +# mock_resp <- structure( +# list(status_code = 200), +# class = "httr2_response" +# ) +# +# mockery::stub(get_parameters_raw, "httr2::req_perform", mock_resp) +# mockery::stub(get_parameters_raw, "httr2::resp_status", 200) +# mockery::stub( +# get_parameters_raw, +# "httr2::resp_body_json", +# stop("Invalid JSON") +# ) +# +# expect_message( +# result <- get_parameters_raw( +# parameter = "hs", +# node = 42, +# start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), +# end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") +# ), +# "Error parsing response" +# ) +# +# expect_null(result) +# }) + +test_that("get_parameters_raw handles API-level error in response", { + mock_resp <- structure( + list(status_code = 200), + class = "httr2_response" + ) + + mock_json <- list( + errorcode = 1, + errormessage = "Invalid node parameter" + ) + + mockery::stub(get_parameters_raw, "httr2::req_perform", mock_resp) + mockery::stub(get_parameters_raw, "httr2::resp_status", 200) + mockery::stub(get_parameters_raw, "httr2::resp_body_json", mock_json) + + expect_message( + result <- get_parameters_raw( + parameter = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "Invalid node parameter" + ) + + expect_null(result) +}) + +test_that("get_parameters handles failure in get_parameters_raw for single parameter", { + # Mock get_parameters_raw to return NULL (simulating any failure) + mockery::stub(get_parameters, "get_parameters_raw", NULL) + + expect_message( + result <- get_parameters( + parameters = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "Failed to retrieve parameter: hs" + ) + + expect_null(result) +}) + +test_that("get_parameters handles partial failure with multiple parameters", { + # First call succeeds, second call fails + mock_success <- tibble::tibble( + time = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + hs = 1.5 + ) + + mockery::stub( + get_parameters, + "get_parameters_raw", + mockery::mock(mock_success, NULL, cycle = TRUE) + ) + + expect_message( + result <- get_parameters( + parameters = c("hs", "tp"), + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "Failed to retrieve parameter: tp" + ) + + expect_null(result) +}) + + +test_that("Errors in 'get_parameters()' are handled correcly", { + expect_error( + get_parameters("tépé"), + "Requested parameters do not exists in the database: tépé" + ) + expect_error( + get_parameters(node = 0), + "The requested location do no exist in the database." + ) + expect_error( + get_parameters(node = c(10, 100)), + "The function can retreive only one location a time." + ) + expect_error( + get_parameters(start = 1), + paste0( + "'start' is outside the covered period: ", + paste( + format( + c( + resourcecode:::rscd_casandra_start_date, + resourcecode:::rscd_casandra_end_date + ), + format = "%Y-%m-%d %H:%M %Z" + ), + collapse = " \u2014 " + ) + ) + ) + expect_error( + get_parameters(end = 1e10), + paste0( + "'end' is outside the covered period: ", + paste( + format( + c( + resourcecode:::rscd_casandra_start_date, + resourcecode:::rscd_casandra_end_date + ), + format = "%Y-%m-%d %H:%M %Z" + ), + collapse = " \u2014 " + ) + ) + ) + expect_error( + get_parameters( + start = "1994-01-31 01:00:00", + end = "1994-01-11 01:00:00" + ), + "'end' must be after 'start'" + ) +}) From c655af12f3d3911cf31596b2008f7c8783132d28 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Mon, 12 Jan 2026 12:08:40 +0100 Subject: [PATCH 2/6] Swith to {httr2} for spectral data download --- DESCRIPTION | 2 +- NEWS.md | 3 +- R/spectral_data_download.R | 101 ++-- tests/testthat/Rplots.pdf | Bin 42019 -> 41797 bytes tests/testthat/_vcr/boundary_dates.yml | 20 + tests/testthat/_vcr/character_dates.yml | 4 +- .../testthat/_vcr/get_multiple_parameters.yml | 8 +- tests/testthat/_vcr/get_single_parameter.yml | 4 +- tests/testthat/_vcr/numeric_dates.yml | 4 +- tests/testthat/_vcr/tp_conversion.yml | 20 + tests/testthat/_vcr/week_of_data.yml | 8 +- tests/testthat/test-data_download.R | 192 -------- tests/testthat/test-spectral_data_download.R | 445 ++++++++++++++++++ tests/testthat/tests_download_parameters.R | 95 ++-- 14 files changed, 624 insertions(+), 282 deletions(-) create mode 100644 tests/testthat/_vcr/boundary_dates.yml create mode 100644 tests/testthat/_vcr/tp_conversion.yml delete mode 100644 tests/testthat/test-data_download.R create mode 100644 tests/testthat/test-spectral_data_download.R diff --git a/DESCRIPTION b/DESCRIPTION index 815fd1d..8ff501e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,7 +20,6 @@ Depends: R (>= 3.6) Imports: abind, - curl, geosphere, ggplot2, grid, @@ -38,6 +37,7 @@ Imports: tibble, tidyr Suggests: + curl, knitr, mockery, rmarkdown, diff --git a/NEWS.md b/NEWS.md index deb9b30..1e6909d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # resourcecode (development version) -- Custom labels are now handeled properly in `cut_seasons()` +- Custom labels are now handled properly in `cut_seasons()` ; +- Switch to {httr2} to download files and API request. # resourcecode 0.5.2 diff --git a/R/spectral_data_download.R b/R/spectral_data_download.R index e9a0565..5a85cdb 100644 --- a/R/spectral_data_download.R +++ b/R/spectral_data_download.R @@ -7,36 +7,29 @@ #' #' @noRd #' @keywords internal -download_nc_data <- function(url, destfile) { - # Ensure destfile exists only if successful - success <- tryCatch( +download_nc <- function(url, destfile) { + tryCatch( { - curl::curl_download(url, destfile = destfile, mode = "wb") - TRUE + req <- httr2::request(url) |> + httr2::req_timeout(60) |> + httr2::req_error(is_error = ~ .x$status_code >= 400) + + httr2::req_perform(req, path = destfile) + + if (!file.exists(destfile)) { + return(NULL) + } + + destfile }, error = function(e) { - message( - "Could not download spectral data. - The remote server may be unavailable or the URL may have changed." - ) - FALSE - }, - warning = function(w) { - message( - "A warning occurred while downloading the spectral data. - The resource may have changed.\n", - w - ) - FALSE + message("Download failed: ", conditionMessage(e)) + if (file.exists(destfile)) { + file.remove(destfile) + } + NULL } ) - - # If download failed, return NULL (do not leave partial file) - if (!success || !file.exists(destfile)) { - NULL - } else { - destfile - } } @@ -68,7 +61,7 @@ get_2d_spectrum_raw <- function(point, year, month) { temp <- tempfile(fileext = ".nc") - file <- download_nc_data(url, temp) + file <- download_nc(url, temp) if (is.null(file)) { message( @@ -145,7 +138,7 @@ get_1d_spectrum_raw <- function(point, year, month) { ) temp <- tempfile(fileext = ".nc") - file <- download_nc_data(url, temp) + file <- download_nc(url, temp) if (is.null(file)) { message( @@ -294,8 +287,35 @@ get_2d_spectrum <- function(point, start = "1994-01-01", end = "1994-02-28") { out <- get_2d_spectrum_raw(point, years[1], months[1]) + if (is.null(out)) { + message( + "Failed to download data for ", + point, + " (", + years[1], + "-", + months[1], + ")" + ) + return(NULL) + } + for (m in seq_along(years[-1])) { temp <- get_2d_spectrum_raw(point, years[m + 1], months[m + 1]) + + if (is.null(temp)) { + message( + "Failed to download data for ", + point, + " (", + years[m + 1], + "-", + months[m + 1], + ")" + ) + return(NULL) + } + out$efth <- abind::abind(out$efth, temp$efth, along = 3) out$forcings <- rbind(out$forcings, temp$forcings) } @@ -396,8 +416,35 @@ get_1d_spectrum <- function(point, start = "1994-01-01", end = "1994-02-28") { out <- get_1d_spectrum_raw(point, years[1], months[1]) + if (is.null(out)) { + message( + "Failed to download data for ", + point, + " (", + years[1], + "-", + months[1], + ")" + ) + return(NULL) + } + for (m in seq_along(years[-1])) { temp <- get_1d_spectrum_raw(point, years[m + 1], months[m + 1]) + + if (is.null(temp)) { + message( + "Failed to download data for ", + point, + " (", + years[m + 1], + "-", + months[m + 1], + ")" + ) + return(NULL) + } + out$ef <- abind::abind(out$ef, temp$ef, along = 2) out$th1m <- abind::abind(out$th1m, temp$th1m, along = 2) out$th2m <- abind::abind(out$th2m, temp$th2m, along = 2) diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index fe295bc179876f0d3628091afdb7eeb6a8f1adc7..546f2c3403b428c6590a1db8ca6c026d135cdf93 100644 GIT binary patch delta 5920 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 SA=}{>NISHxZ0!DDLjFI}DlmBf delta 9320 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 start" + ) +}) + +test_that("get_1d_spectrum validates start date is within coverage", { + expect_error( + get_1d_spectrum( + "SEMREVO", + start = "1980-01-01", # Before hindcast period + end = "1994-01-31" + ), + "format\\(start, \"%Y\"\\) >= format\\(rscd_hindcast_start_date" + ) +}) + +test_that("get_1d_spectrum validates end date is within coverage", { + expect_error( + get_1d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "2030-01-01" # After hindcast period + ), + "format\\(end, \"%Y\"\\) <= format\\(rscd_hindcast_end_date" + ) +}) + +# Network failure tests - using mocks (no vcr needed) +test_that("get_1d_spectrum fails gracefully when first download fails", { + # Mock the internal raw function to return NULL (simulating download failure) + mockery::stub( + get_1d_spectrum, + "get_1d_spectrum_raw", + NULL + ) + + # Should fail or return NULL depending on your implementation + # Update this based on how you handle NULL in get_1d_spectrum + expect_null( + get_1d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + ) +}) + +# Tests for get_2d_spectrum() +test_that("get_2d_spectrum retrieves data successfully", { + # vcr::local_cassette("get_2d_spectrum_basic") + + spec <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-02-28" + ) + + expect_type(spec, "list") + expect_named( + spec, + c( + "longitude", + "latitude", + "frequency1", + "frequency2", + "efth", + "freq", + "dir", + "forcings", + "station" + ) + ) + expect_s3_class(spec$forcings, "data.frame") + expect_shape(spec$forcings, dim = c(1416, 6)) + expect_equal(spec$station, "SEMREVO") +}) + +test_that("get_2d_spectrum handles numeric node input", { + # vcr::local_cassette("get_2d_spectrum_numeric_node") + + spec_by_name <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + + idx <- which(resourcecodedata::rscd_spectral$name == "SEMREVO") + + spec_by_index <- get_2d_spectrum( + idx, + start = "1994-01-01", + end = "1994-01-31" + ) + + expect_equal(spec_by_name$station, spec_by_index$station) +}) + +test_that("get_2d_spectrum handles character date inputs", { + # vcr::local_cassette("get_2d_spectrum_character_dates") + + spec <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + + expect_type(spec, "list") + expect_shape(spec$forcings, dim = c(744, 6)) +}) + +test_that("get_2d_spectrum handles numeric date inputs", { + # vcr::local_cassette("get_2d_spectrum_numeric_dates") + + start_unix <- as.numeric(as.POSIXct("1994-01-01", tz = "UTC")) + end_unix <- as.numeric(as.POSIXct("1994-01-31", tz = "UTC")) + + spec <- get_2d_spectrum( + "SEMREVO", + start = start_unix, + end = end_unix + ) + + expect_type(spec, "list") + expect_gt(nrow(spec$forcings), 0) +}) + +test_that("get_2d_spectrum handles multi-month requests", { + # vcr::local_cassette("get_2d_spectrum_multi_month") + + spec <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-03-31" + ) + + expect_type(spec, "list") + expect_gt(nrow(spec$forcings), 2000) +}) + +test_that("get_2d_spectrum validates forcings data structure", { + # vcr::local_cassette("get_2d_spectrum_forcings_structure") + + spec <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + + # Check forcings has expected columns + expected_cols <- c("time", "dpt", "wnd", "wnddir", "cur", "curdir") + expect_true(all(expected_cols %in% names(spec$forcings))) + + # Check time is POSIXct + expect_s3_class(spec$forcings$time, "POSIXct") +}) + +test_that("get_2d_spectrum validates spectral array dimensions", { + # vcr::local_cassette("get_2d_spectrum_array_dimensions") + + spec <- get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + + # Check that spectral array has correct dimensions + expect_true(is.array(spec$efth)) + expect_equal(length(dim(spec$efth)), 3) + expect_equal(dim(spec$efth)[1], length(spec$dir)) + expect_equal(dim(spec$efth)[2], length(spec$freq)) + expect_equal(dim(spec$efth)[3], nrow(spec$forcings)) +}) + +# Error handling tests - input validation (no network needed) +test_that("get_2d_spectrum validates point input length", { + expect_error( + get_2d_spectrum( + c("SEMREVO", "AUTRE"), + start = "1994-01-01", + end = "1994-01-31" + ), + "length\\(point\\) == 1" + ) +}) + +test_that("get_2d_spectrum validates point exists", { + expect_error( + get_2d_spectrum( + "INVALID_POINT", + start = "1994-01-01", + end = "1994-01-31" + ), + "point %in% resourcecodedata::rscd_spectral\\$name" + ) +}) + +test_that("get_2d_spectrum validates date range", { + expect_error( + get_2d_spectrum( + "SEMREVO", + start = "1994-01-31", + end = "1994-01-01" + ), + "end >= start" + ) +}) + +test_that("get_2d_spectrum validates start date within coverage", { + expect_error( + get_2d_spectrum( + "SEMREVO", + start = "1980-01-01", + end = "1994-01-31" + ), + "format\\(start, \"%Y\"\\) >= format\\(rscd_hindcast_start_date" + ) +}) + +test_that("get_2d_spectrum validates end date within coverage", { + expect_error( + get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "2030-01-01" + ), + "format\\(end, \"%Y\"\\) <= format\\(rscd_hindcast_end_date" + ) +}) + +# Network failure tests - using mocks (no vcr needed) +test_that("get_2d_spectrum fails gracefully when first download fails", { + mockery::stub( + get_2d_spectrum, + "get_2d_spectrum_raw", + NULL + ) + + expect_null( + get_2d_spectrum( + "SEMREVO", + start = "1994-01-01", + end = "1994-01-31" + ) + ) +}) + +# Edge case: Boundary dates +test_that("get_1d_spectrum accepts dates at exact boundaries", { + # vcr::local_cassette("get_1d_spectrum_boundary_dates") + + # Test with start date at exact boundary + expect_no_error( + get_1d_spectrum( + "SEMREVO", + start = format(resourcecode:::rscd_hindcast_start_date, "%Y-01-01"), + end = format(resourcecode:::rscd_hindcast_start_date, "%Y-01-31") + ) + ) +}) + +test_that("get_2d_spectrum accepts dates at exact boundaries", { + # vcr::local_cassette("get_2d_spectrum_boundary_dates") + + # Test with start date at exact boundary + expect_no_error( + get_2d_spectrum( + "SEMREVO", + start = format(resourcecode:::rscd_hindcast_start_date, "%Y-01-01"), + end = format(resourcecode:::rscd_hindcast_start_date, "%Y-01-31") + ) + ) +}) diff --git a/tests/testthat/tests_download_parameters.R b/tests/testthat/tests_download_parameters.R index c7481cb..664e08d 100644 --- a/tests/testthat/tests_download_parameters.R +++ b/tests/testthat/tests_download_parameters.R @@ -124,27 +124,6 @@ test_that("get_parameters produces expected data structure over time range", { expect_type(result$tp, "double") }) -# Error handling tests using mockery -# test_that("get_parameters_raw handles network timeout", { -# mockery::stub( -# get_parameters_raw, -# "httr2::req_perform", -# stop("Timeout was reached") -# ) -# -# expect_message( -# result <- get_parameters_raw( -# parameter = "hs", -# node = 42, -# start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), -# end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") -# ), -# "Network error" -# ) -# -# expect_null(result) -# }) - test_that("get_parameters_raw handles HTTP 404 error", { mock_resp <- structure( list(status_code = 404), @@ -195,32 +174,26 @@ test_that("get_parameters_raw handles HTTP 500 server error", { expect_null(result) }) -# test_that("get_parameters_raw handles invalid JSON response", { -# mock_resp <- structure( -# list(status_code = 200), -# class = "httr2_response" -# ) -# -# mockery::stub(get_parameters_raw, "httr2::req_perform", mock_resp) -# mockery::stub(get_parameters_raw, "httr2::resp_status", 200) -# mockery::stub( -# get_parameters_raw, -# "httr2::resp_body_json", -# stop("Invalid JSON") -# ) -# -# expect_message( -# result <- get_parameters_raw( -# parameter = "hs", -# node = 42, -# start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), -# end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") -# ), -# "Error parsing response" -# ) -# -# expect_null(result) -# }) +test_that("get_parameters handles network connection failure gracefully", { + # Mock the internal function to simulate network failure + mockery::stub( + get_parameters, + "get_parameters_raw", + NULL # Simulates what get_parameters_raw returns on network failure + ) + + expect_message( + result <- get_parameters( + parameters = "hs", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ), + "Failed to retrieve parameter: hs" + ) + + expect_null(result) +}) test_that("get_parameters_raw handles API-level error in response", { mock_resp <- structure( @@ -347,3 +320,31 @@ test_that("Errors in 'get_parameters()' are handled correcly", { "'end' must be after 'start'" ) }) + +test_that("get_parameters accepts dates at exact boundaries", { + vcr::local_cassette("boundary_dates") + + expect_no_error( + get_parameters( + parameters = "hs", + node = 42, + start = resourcecode:::rscd_casandra_start_date, + end = resourcecode:::rscd_casandra_start_date + 3600 # 1 hour later + ) + ) +}) + +test_that("tp parameter conversion handles edge cases", { + vcr::local_cassette("tp_conversion") + + result <- get_parameters( + parameters = "tp", + node = 42, + start = as.POSIXct("1994-01-01 00:00:00", tz = "UTC"), + end = as.POSIXct("1994-01-02 00:00:00", tz = "UTC") + ) + + # All tp values should be positive and finite + expect_true(all(is.finite(result$tp))) + expect_true(all(result$tp > 0)) +}) From d320e9b295aaecd8fadd91068b0a46b9a262fdaf Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Mon, 12 Jan 2026 12:10:52 +0100 Subject: [PATCH 3/6] Make lint happier --- tests/testthat/test-spectral_data_download.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-spectral_data_download.R b/tests/testthat/test-spectral_data_download.R index 2de43ca..16d40a9 100644 --- a/tests/testthat/test-spectral_data_download.R +++ b/tests/testthat/test-spectral_data_download.R @@ -1,6 +1,6 @@ # Tests for get_1d_spectrum() test_that("get_1d_spectrum retrieves data successfully", { - #vcr::local_cassette("get_1d_spectrum_basic", match_requests_on = c("method", "uri", "path", "body")) + #vcr::local_cassette("get_1d_spectrum_basic") spec <- get_1d_spectrum( "SEMREVO", From 9cf88358b3ea74cdd6e38bab885f42dbbffbfff0 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Mon, 12 Jan 2026 12:11:57 +0100 Subject: [PATCH 4/6] Remove {curl} from dependencies --- DESCRIPTION | 1 - 1 file changed, 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8ff501e..c382168 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,7 +37,6 @@ Imports: tibble, tidyr Suggests: - curl, knitr, mockery, rmarkdown, From 8b161b4b63ee9be6a40eb861280e3bf5a7ffc398 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Mon, 12 Jan 2026 13:33:32 +0100 Subject: [PATCH 5/6] Remove all traces of {curl} in examples --- R/download_data.R | 7 ++++--- R/spectral_data_download.R | 34 +++++++++++++++++++--------------- man/get_1d_spectrum.Rd | 22 +++++++++++----------- man/get_2d_spectrum.Rd | 12 ++++++------ man/get_parameters.Rd | 6 ++---- tests/testthat/Rplots.pdf | Bin 41797 -> 41797 bytes vignettes/resourcecode.Rmd | 26 ++++++++++++++------------ 7 files changed, 56 insertions(+), 51 deletions(-) diff --git a/R/download_data.R b/R/download_data.R index 9fad88e..193f134 100644 --- a/R/download_data.R +++ b/R/download_data.R @@ -47,6 +47,7 @@ get_parameters_raw <- function( httr2::req_error(is_error = \(resp) FALSE) |> # Don't auto-error on HTTP errors httr2::req_retry(max_tries = 3) |> # Retry transient failures httr2::req_timeout(30) |> # 30 second timeout + httr2::req_user_agent("Resourcecode R package") |> httr2::req_perform(), httr2_failure = function(cnd) { message( @@ -133,9 +134,9 @@ get_parameters_raw <- function( #' @return a tibble with N-rows and `length(parameters)` columns. #' @export #' -#' @examplesIf curl::has_internet() -#' ts <- get_parameters(parameters = c("hs", "tp"), node = 42) -#' plot(ts$time, ts$hs, type = "l") +#' @examples +#' rscd_data <- get_parameters(parameters = c("hs", "tp"), node = 42) +#' if(!is.null(rscd_data)) plot(rscd_data$time, rscd_data$hs, type = "l") get_parameters <- function( parameters = "hs", node = 42, diff --git a/R/spectral_data_download.R b/R/spectral_data_download.R index 5a85cdb..c66b423 100644 --- a/R/spectral_data_download.R +++ b/R/spectral_data_download.R @@ -240,12 +240,14 @@ get_1d_spectrum_raw <- function(point, year, month) { #' } #' @export #' -#' @examplesIf curl::has_internet() +#' @examples #' spec2D <- get_2d_spectrum("SEMREVO", start = "1994-01-01", end = "1994-02-28") -#' image(spec2D$dir, spec2D$freq, spec2D$efth[, , 1], -#' xlab = "Direction (°)", -#' ylab = "Frequency (Hz" -#' ) +#' if(!is.null(spec2D)){ +#' image(spec2D$dir, spec2D$freq, spec2D$efth[, , 1], +#' xlab = "Direction (°)", +#' ylab = "Frequency (Hz" +#' ) +#' } get_2d_spectrum <- function(point, start = "1994-01-01", end = "1994-02-28") { stopifnot(length(point) == 1) @@ -364,17 +366,19 @@ get_2d_spectrum <- function(point, start = "1994-01-01", end = "1994-02-28") { #' } #' @export #' -#' @examplesIf curl::has_internet() +#' @examples #' spec1D <- get_1d_spectrum("SEMREVO", start = "1994-01-01", end = "1994-02-28") -#' r <- as.POSIXct(round(range(spec1D$forcings$time), "month")) -#' image(spec1D$forcings$time, spec1D$freq, t(spec1D$ef), -#' xaxt = "n", xlab = "Time", -#' ylab = "Frequency (Hz)" -#' ) -#' axis.POSIXct(1, spec1D$forcings$time, -#' at = seq(r[1], r[2], by = "week"), -#' format = "%Y-%m-%d", las = 2 -#' ) +#' if(!is.null(spec1D)){ +#' r <- as.POSIXct(round(range(spec1D$forcings$time), "month")) +#' image(spec1D$forcings$time, spec1D$freq, t(spec1D$ef), +#' xaxt = "n", xlab = "Time", +#' ylab = "Frequency (Hz)" +#' ) +#' axis.POSIXct(1, spec1D$forcings$time, +#' at = seq(r[1], r[2], by = "week"), +#' format = "%Y-%m-%d", las = 2 +#' ) +#' } get_1d_spectrum <- function(point, start = "1994-01-01", end = "1994-02-28") { stopifnot(length(point) == 1) diff --git a/man/get_1d_spectrum.Rd b/man/get_1d_spectrum.Rd index 33acefa..84b3020 100644 --- a/man/get_1d_spectrum.Rd +++ b/man/get_1d_spectrum.Rd @@ -51,16 +51,16 @@ A list with 12 elements: Download the 1D spectrum data from IFREMER ftp } \examples{ -\dontshow{if (curl::has_internet()) withAutoprint(\{ # examplesIf} spec1D <- get_1d_spectrum("SEMREVO", start = "1994-01-01", end = "1994-02-28") -r <- as.POSIXct(round(range(spec1D$forcings$time), "month")) -image(spec1D$forcings$time, spec1D$freq, t(spec1D$ef), - xaxt = "n", xlab = "Time", - ylab = "Frequency (Hz)" -) -axis.POSIXct(1, spec1D$forcings$time, - at = seq(r[1], r[2], by = "week"), - format = "\%Y-\%m-\%d", las = 2 -) -\dontshow{\}) # examplesIf} +if(!is.null(spec1D)){ + r <- as.POSIXct(round(range(spec1D$forcings$time), "month")) + image(spec1D$forcings$time, spec1D$freq, t(spec1D$ef), + xaxt = "n", xlab = "Time", + ylab = "Frequency (Hz)" + ) + axis.POSIXct(1, spec1D$forcings$time, + at = seq(r[1], r[2], by = "week"), + format = "\%Y-\%m-\%d", las = 2 + ) +} } diff --git a/man/get_2d_spectrum.Rd b/man/get_2d_spectrum.Rd index b0428d6..332538a 100644 --- a/man/get_2d_spectrum.Rd +++ b/man/get_2d_spectrum.Rd @@ -44,11 +44,11 @@ A list with 9 elements: Download the 2D spectrum data from IFREMER ftp } \examples{ -\dontshow{if (curl::has_internet()) withAutoprint(\{ # examplesIf} spec2D <- get_2d_spectrum("SEMREVO", start = "1994-01-01", end = "1994-02-28") -image(spec2D$dir, spec2D$freq, spec2D$efth[, , 1], - xlab = "Direction (°)", - ylab = "Frequency (Hz" -) -\dontshow{\}) # examplesIf} +if(!is.null(spec2D)){ + image(spec2D$dir, spec2D$freq, spec2D$efth[, , 1], + xlab = "Direction (°)", + ylab = "Frequency (Hz" + ) +} } diff --git a/man/get_parameters.Rd b/man/get_parameters.Rd index abdada5..0c6eb98 100644 --- a/man/get_parameters.Rd +++ b/man/get_parameters.Rd @@ -28,8 +28,6 @@ If the remote resource is unavailable or returns an error, the function returns and emits an informative message. } \examples{ -\dontshow{if (curl::has_internet()) withAutoprint(\{ # examplesIf} -ts <- get_parameters(parameters = c("hs", "tp"), node = 42) -plot(ts$time, ts$hs, type = "l") -\dontshow{\}) # examplesIf} +rscd_data <- get_parameters(parameters = c("hs", "tp"), node = 42) +if(!is.null(rscd_data)) plot(rscd_data$time, rscd_data$hs, type = "l") } diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index 546f2c3403b428c6590a1db8ca6c026d135cdf93..426c6a28aede2aa7146317b4f4f9ee35bf5b9a7b 100644 GIT binary patch delta 25 dcmX?ljOpkxrU_Oo#>Pg*6YV6x^v0;yivV}*2?78B delta 25 dcmX?ljOpkxrU_OoMh3 Date: Mon, 12 Jan 2026 15:30:46 +0100 Subject: [PATCH 6/6] Refactor tests to be skipped when offline --- R/spectral_data_download.R | 3 + tests/testthat/Rplots.pdf | Bin 41797 -> 41797 bytes tests/testthat/_vcr/boundary_dates.yml | 4 +- tests/testthat/_vcr/character_dates.yml | 4 +- .../testthat/_vcr/get_multiple_parameters.yml | 8 +-- tests/testthat/_vcr/get_single_parameter.yml | 4 +- tests/testthat/_vcr/numeric_dates.yml | 4 +- tests/testthat/_vcr/tp_conversion.yml | 4 +- tests/testthat/_vcr/week_of_data.yml | 9 +-- tests/testthat/test-spectral_data_download.R | 57 ++++++------------ 10 files changed, 39 insertions(+), 58 deletions(-) diff --git a/R/spectral_data_download.R b/R/spectral_data_download.R index c66b423..b29ae96 100644 --- a/R/spectral_data_download.R +++ b/R/spectral_data_download.R @@ -11,7 +11,10 @@ download_nc <- function(url, destfile) { tryCatch( { req <- httr2::request(url) |> + httr2::req_error(is_error = \(resp) FALSE) |> # Don't auto-error on HTTP errors + httr2::req_retry(max_tries = 3) |> # Retry transient failures httr2::req_timeout(60) |> + httr2::req_user_agent("Resourcecode R package") |> httr2::req_error(is_error = ~ .x$status_code >= 400) httr2::req_perform(req, path = destfile) diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index 426c6a28aede2aa7146317b4f4f9ee35bf5b9a7b..3d73607e715d73e14762604079bcc135af995577 100644 GIT binary patch delta 25 dcmX?ljOpkxrU_Oorbb4F6YV6x^v0;yivV}x2><{9 delta 25 dcmX?ljOpkxrU_Oo#>Pg*6YV6x^v0;yivV}*2?78B diff --git a/tests/testthat/_vcr/boundary_dates.yml b/tests/testthat/_vcr/boundary_dates.yml index 0d5ab7a..fa120d3 100644 --- a/tests/testthat/_vcr/boundary_dates.yml +++ b/tests/testthat/_vcr/boundary_dates.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:11 GMT + Date: Mon, 12 Jan 2026 14:22:22 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,5 +16,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757386000000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-01T01:00:00Z"},"result":{"dataSetSizeBeforeRegression":2,"dataSetSizeAfterRegression":2,"dataSetSize":2,"data":[[757382400000,0.124],[757386000000,0.112]]}}' - recorded_at: 2026-01-12 11:03:16 + recorded_at: 2026-01-12 14:22:22 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/character_dates.yml b/tests/testthat/_vcr/character_dates.yml index c68e2b0..69fd7c9 100644 --- a/tests/testthat/_vcr/character_dates.yml +++ b/tests/testthat/_vcr/character_dates.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:10 GMT + Date: Mon, 12 Jan 2026 14:22:20 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,5 +16,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' - recorded_at: 2026-01-12 11:03:10 + recorded_at: 2026-01-12 14:22:20 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/get_multiple_parameters.yml b/tests/testthat/_vcr/get_multiple_parameters.yml index 182258f..ade0d15 100644 --- a/tests/testthat/_vcr/get_multiple_parameters.yml +++ b/tests/testthat/_vcr/get_multiple_parameters.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:09 GMT + Date: Mon, 12 Jan 2026 14:22:18 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,14 +16,14 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' - recorded_at: 2026-01-12 11:03:10 + recorded_at: 2026-01-12 14:22:18 - request: method: GET uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=fp&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-02T00%3A00%3A00Z response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:10 GMT + Date: Mon, 12 Jan 2026 14:22:18 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -34,5 +34,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"fp","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.067],[757386000000,0.067],[757389600000,0.068],[757393200000,0.069],[757396800000,0.069],[757400400000,0.069],[757404000000,0.068],[757407600000,0.068],[757411200000,0.068],[757414800000,0.068],[757418400000,0.068],[757422000000,0.069],[757425600000,0.07],[757429200000,0.071],[757432800000,0.072],[757436400000,0.073],[757440000000,0.074],[757443600000,0.074],[757447200000,0.074],[757450800000,0.073],[757454400000,0.072],[757458000000,0.072],[757461600000,0.072],[757465200000,0.072],[757468800000,0.072]]}}' - recorded_at: 2026-01-12 11:03:10 + recorded_at: 2026-01-12 14:22:19 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/get_single_parameter.yml b/tests/testthat/_vcr/get_single_parameter.yml index 2fb6bb7..5f5edcf 100644 --- a/tests/testthat/_vcr/get_single_parameter.yml +++ b/tests/testthat/_vcr/get_single_parameter.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:09 GMT + Date: Mon, 12 Jan 2026 14:22:18 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,5 +16,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' - recorded_at: 2026-01-12 11:03:09 + recorded_at: 2026-01-12 14:22:18 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/numeric_dates.yml b/tests/testthat/_vcr/numeric_dates.yml index c68e2b0..69fd7c9 100644 --- a/tests/testthat/_vcr/numeric_dates.yml +++ b/tests/testthat/_vcr/numeric_dates.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:10 GMT + Date: Mon, 12 Jan 2026 14:22:20 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,5 +16,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034]]}}' - recorded_at: 2026-01-12 11:03:10 + recorded_at: 2026-01-12 14:22:20 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/tp_conversion.yml b/tests/testthat/_vcr/tp_conversion.yml index 47d6eb9..d423ff1 100644 --- a/tests/testthat/_vcr/tp_conversion.yml +++ b/tests/testthat/_vcr/tp_conversion.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:16 GMT + Date: Mon, 12 Jan 2026 14:22:22 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,5 +16,5 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"fp","startTimeMillis":"757382400000","endTimeMillis":"757468800000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-02T00:00:00Z"},"result":{"dataSetSizeBeforeRegression":25,"dataSetSizeAfterRegression":25,"dataSetSize":25,"data":[[757382400000,0.067],[757386000000,0.067],[757389600000,0.068],[757393200000,0.069],[757396800000,0.069],[757400400000,0.069],[757404000000,0.068],[757407600000,0.068],[757411200000,0.068],[757414800000,0.068],[757418400000,0.068],[757422000000,0.069],[757425600000,0.07],[757429200000,0.071],[757432800000,0.072],[757436400000,0.073],[757440000000,0.074],[757443600000,0.074],[757447200000,0.074],[757450800000,0.073],[757454400000,0.072],[757458000000,0.072],[757461600000,0.072],[757465200000,0.072],[757468800000,0.072]]}}' - recorded_at: 2026-01-12 11:03:16 + recorded_at: 2026-01-12 14:22:22 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/_vcr/week_of_data.yml b/tests/testthat/_vcr/week_of_data.yml index e17d4b7..ac0afca 100644 --- a/tests/testthat/_vcr/week_of_data.yml +++ b/tests/testthat/_vcr/week_of_data.yml @@ -5,7 +5,7 @@ http_interactions: response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:10 GMT + Date: Mon, 12 Jan 2026 14:22:20 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -16,14 +16,14 @@ http_interactions: Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"hs","startTimeMillis":"757382400000","endTimeMillis":"757983600000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-07T23:00:00Z"},"result":{"dataSetSizeBeforeRegression":168,"dataSetSizeAfterRegression":168,"dataSetSize":168,"data":[[757382400000,0.124],[757386000000,0.112],[757389600000,0.098],[757393200000,0.082],[757396800000,0.07],[757400400000,0.058],[757404000000,0.052],[757407600000,0.05],[757411200000,0.048],[757414800000,0.046],[757418400000,0.046],[757422000000,0.044],[757425600000,0.044],[757429200000,0.044],[757432800000,0.042],[757436400000,0.038],[757440000000,0.034],[757443600000,0.028],[757447200000,0.024],[757450800000,0.022],[757454400000,0.022],[757458000000,0.022],[757461600000,0.026],[757465200000,0.03],[757468800000,0.034],[757472400000,0.036],[757476000000,0.036],[757479600000,0.034],[757483200000,0.03],[757486800000,0.026],[757490400000,0.022],[757494000000,0.018],[757497600000,0.018],[757501200000,0.02],[757504800000,0.024],[757508400000,0.028],[757512000000,0.032],[757515600000,0.034],[757519200000,0.034],[757522800000,0.032],[757526400000,0.032],[757530000000,0.036],[757533600000,0.056],[757537200000,0.066],[757540800000,0.062],[757544400000,0.058],[757548000000,0.058],[757551600000,0.058],[757555200000,0.058],[757558800000,0.062],[757562400000,0.062],[757566000000,0.058],[757569600000,0.052],[757573200000,0.042],[757576800000,0.032],[757580400000,0.026],[757584000000,0.022],[757587600000,0.02],[757591200000,0.022],[757594800000,0.024],[757598400000,0.028],[757602000000,0.032],[757605600000,0.034],[757609200000,0.034],[757612800000,0.032],[757616400000,0.03],[757620000000,0.028],[757623600000,0.024],[757627200000,0.024],[757630800000,0.024],[757634400000,0.026],[757638000000,0.03],[757641600000,0.034],[757645200000,0.036],[757648800000,0.038],[757652400000,0.04],[757656000000,0.038],[757659600000,0.036],[757663200000,0.032],[757666800000,0.028],[757670400000,0.026],[757674000000,0.03],[757677600000,0.068],[757681200000,0.08],[757684800000,0.082],[757688400000,0.088],[757692000000,0.096],[757695600000,0.104],[757699200000,0.11],[757702800000,0.112],[757706400000,0.112],[757710000000,0.108],[757713600000,0.1],[757717200000,0.096],[757720800000,0.1],[757724400000,0.108],[757728000000,0.116],[757731600000,0.124],[757735200000,0.126],[757738800000,0.128],[757742400000,0.124],[757746000000,0.114],[757749600000,0.106],[757753200000,0.102],[757756800000,0.1],[757760400000,0.094],[757764000000,0.092],[757767600000,0.094],[757771200000,0.094],[757774800000,0.092],[757778400000,0.09],[757782000000,0.088],[757785600000,0.084],[757789200000,0.08],[757792800000,0.074],[757796400000,0.074],[757800000000,0.074],[757803600000,0.078],[757807200000,0.076],[757810800000,0.074],[757814400000,0.074],[757818000000,0.072],[757821600000,0.066],[757825200000,0.064],[757828800000,0.068],[757832400000,0.074],[757836000000,0.076],[757839600000,0.076],[757843200000,0.074],[757846800000,0.074],[757850400000,0.068],[757854000000,0.06],[757857600000,0.056],[757861200000,0.05],[757864800000,0.046],[757868400000,0.04],[757872000000,0.038],[757875600000,0.034],[757879200000,0.032],[757882800000,0.03],[757886400000,0.028],[757890000000,0.024],[757893600000,0.022],[757897200000,0.018],[757900800000,0.016],[757904400000,0.016],[757908000000,0.016],[757911600000,0.018],[757915200000,0.02],[757918800000,0.022],[757922400000,0.024],[757926000000,0.024],[757929600000,0.024],[757933200000,0.022],[757936800000,0.02],[757940400000,0.018],[757944000000,0.016],[757947600000,0.016],[757951200000,0.018],[757954800000,0.02],[757958400000,0.024],[757962000000,0.028],[757965600000,0.03],[757969200000,0.032],[757972800000,0.03],[757976400000,0.026],[757980000000,0.024],[757983600000,0.022]]}}' - recorded_at: 2026-01-12 11:03:11 + recorded_at: 2026-01-12 14:22:20 - request: method: GET uri: https://resourcecode-datacharts.ifremer.fr/api/timeseries?parameter=fp&node=41&start=1994-01-01T00%3A00%3A00Z&end=1994-01-07T23%3A00%3A00Z response: status: 200 headers: - Date: Mon, 12 Jan 2026 11:03:11 GMT + Date: Mon, 12 Jan 2026 14:22:20 GMT Server: Microsoft-IIS/8.0 Content-Type: application/json;charset=UTF-8 Vary: Accept-Encoding @@ -31,8 +31,9 @@ http_interactions: X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Access-Control-Allow-Origin: '*' + Connection: close Transfer-Encoding: chunked body: string: '{"errorcode":0,"errormessage":"","query":{"downsampling":"none","samplingparameter":"none","platform":"41","parameterCode":"fp","startTimeMillis":"757382400000","endTimeMillis":"757983600000","startTimeFormat":"1994-01-01T00:00:00Z","endTimeFormat":"1994-01-07T23:00:00Z"},"result":{"dataSetSizeBeforeRegression":168,"dataSetSizeAfterRegression":168,"dataSetSize":168,"data":[[757382400000,0.067],[757386000000,0.067],[757389600000,0.068],[757393200000,0.069],[757396800000,0.069],[757400400000,0.069],[757404000000,0.068],[757407600000,0.068],[757411200000,0.068],[757414800000,0.068],[757418400000,0.068],[757422000000,0.069],[757425600000,0.07],[757429200000,0.071],[757432800000,0.072],[757436400000,0.073],[757440000000,0.074],[757443600000,0.074],[757447200000,0.074],[757450800000,0.073],[757454400000,0.072],[757458000000,0.072],[757461600000,0.072],[757465200000,0.072],[757468800000,0.072],[757472400000,0.072],[757476000000,0.072],[757479600000,0.072],[757483200000,0.071],[757486800000,0.069],[757490400000,0.069],[757494000000,0.069],[757497600000,0.069],[757501200000,0.07],[757504800000,0.071],[757508400000,0.072],[757512000000,0.073],[757515600000,0.074],[757519200000,0.074],[757522800000,0.075],[757526400000,0.077],[757530000000,0.079],[757533600000,0.08],[757537200000,0.862],[757540800000,0.821],[757544400000,0.796],[757548000000,0.799],[757551600000,0.081],[757555200000,0.081],[757558800000,0.081],[757562400000,0.082],[757566000000,0.082],[757569600000,0.083],[757573200000,0.084],[757576800000,0.085],[757580400000,0.085],[757584000000,0.085],[757587600000,0.046],[757591200000,0.047],[757594800000,0.049],[757598400000,0.049],[757602000000,0.05],[757605600000,0.05],[757609200000,0.051],[757612800000,0.052],[757616400000,0.053],[757620000000,0.054],[757623600000,0.055],[757627200000,0.056],[757630800000,0.056],[757634400000,0.057],[757638000000,0.059],[757641600000,0.059],[757645200000,0.06],[757648800000,0.06],[757652400000,0.061],[757656000000,0.061],[757659600000,0.062],[757663200000,0.062],[757666800000,0.062],[757670400000,0.062],[757674000000,0.062],[757677600000,0.063],[757681200000,0.064],[757684800000,0.065],[757688400000,0.066],[757692000000,0.066],[757695600000,0.067],[757699200000,0.652],[757702800000,0.643],[757706400000,0.639],[757710000000,0.647],[757713600000,0.655],[757717200000,0.662],[757720800000,0.66],[757724400000,0.646],[757728000000,0.616],[757731600000,0.605],[757735200000,0.6],[757738800000,0.597],[757742400000,0.599],[757746000000,0.607],[757749600000,0.62],[757753200000,0.651],[757756800000,0.661],[757760400000,0.665],[757764000000,0.669],[757767600000,0.668],[757771200000,0.664],[757774800000,0.664],[757778400000,0.667],[757782000000,0.671],[757785600000,0.676],[757789200000,0.688],[757792800000,0.709],[757796400000,0.725],[757800000000,0.729],[757803600000,0.729],[757807200000,0.722],[757810800000,0.723],[757814400000,0.722],[757818000000,0.715],[757821600000,0.713],[757825200000,0.721],[757828800000,0.734],[757832400000,0.733],[757836000000,0.728],[757839600000,0.724],[757843200000,0.724],[757846800000,0.726],[757850400000,0.728],[757854000000,0.733],[757857600000,0.74],[757861200000,0.755],[757864800000,0.775],[757868400000,0.783],[757872000000,0.09],[757875600000,0.09],[757879200000,0.072],[757882800000,0.072],[757886400000,0.073],[757890000000,0.073],[757893600000,0.074],[757897200000,0.074],[757900800000,0.075],[757904400000,0.075],[757908000000,0.075],[757911600000,0.074],[757915200000,0.07],[757918800000,0.069],[757922400000,0.069],[757926000000,0.069],[757929600000,0.071],[757933200000,0.072],[757936800000,0.073],[757940400000,0.073],[757944000000,0.073],[757947600000,0.073],[757951200000,0.073],[757954800000,0.073],[757958400000,0.073],[757962000000,0.073],[757965600000,0.074],[757969200000,0.075],[757972800000,0.076],[757976400000,0.077],[757980000000,0.077],[757983600000,0.078]]}}' - recorded_at: 2026-01-12 11:03:11 + recorded_at: 2026-01-12 14:22:21 recorded_with: VCR-vcr/2.1.0 diff --git a/tests/testthat/test-spectral_data_download.R b/tests/testthat/test-spectral_data_download.R index 16d40a9..2113c27 100644 --- a/tests/testthat/test-spectral_data_download.R +++ b/tests/testthat/test-spectral_data_download.R @@ -1,6 +1,6 @@ # Tests for get_1d_spectrum() test_that("get_1d_spectrum retrieves data successfully", { - #vcr::local_cassette("get_1d_spectrum_basic") + skip_if_offline() spec <- get_1d_spectrum( "SEMREVO", @@ -33,6 +33,7 @@ test_that("get_1d_spectrum retrieves data successfully", { test_that("get_1d_spectrum handles numeric node input", { #vcr::local_cassette("get_1d_spectrum_numeric_node") + skip_if_offline() spec_by_name <- get_1d_spectrum( "SEMREVO", @@ -54,6 +55,7 @@ test_that("get_1d_spectrum handles numeric node input", { test_that("get_1d_spectrum handles character date inputs", { #vcr::local_cassette("get_1d_spectrum_character_dates") + skip_if_offline() spec <- get_1d_spectrum( "SEMREVO", @@ -67,6 +69,7 @@ test_that("get_1d_spectrum handles character date inputs", { test_that("get_1d_spectrum handles numeric (UNIX timestamp) date inputs", { # vcr::local_cassette("get_1d_spectrum_numeric_dates") + skip_if_offline() start_unix <- as.numeric(as.POSIXct("1994-01-01", tz = "UTC")) end_unix <- as.numeric(as.POSIXct("1994-01-31", tz = "UTC")) @@ -83,6 +86,7 @@ test_that("get_1d_spectrum handles numeric (UNIX timestamp) date inputs", { test_that("get_1d_spectrum handles multi-month requests", { # vcr::local_cassette("get_1d_spectrum_multi_month") + skip_if_offline() spec <- get_1d_spectrum( "SEMREVO", @@ -97,6 +101,7 @@ test_that("get_1d_spectrum handles multi-month requests", { test_that("get_1d_spectrum validates forcings data structure", { # vcr::local_cassette("get_1d_spectrum_forcings_structure") + skip_if_offline() spec <- get_1d_spectrum( "SEMREVO", @@ -129,6 +134,7 @@ test_that("get_1d_spectrum validates forcings data structure", { test_that("get_1d_spectrum validates spectral arrays dimensions", { # vcr::local_cassette("get_1d_spectrum_array_dimensions") + skip_if_offline() spec <- get_1d_spectrum( "SEMREVO", @@ -198,7 +204,7 @@ test_that("get_1d_spectrum validates end date is within coverage", { ) }) -# Network failure tests - using mocks (no vcr needed) +# Network failure tests - using mocks test_that("get_1d_spectrum fails gracefully when first download fails", { # Mock the internal raw function to return NULL (simulating download failure) mockery::stub( @@ -221,11 +227,12 @@ test_that("get_1d_spectrum fails gracefully when first download fails", { # Tests for get_2d_spectrum() test_that("get_2d_spectrum retrieves data successfully", { # vcr::local_cassette("get_2d_spectrum_basic") + skip_if_offline() spec <- get_2d_spectrum( "SEMREVO", start = "1994-01-01", - end = "1994-02-28" + end = "1994-01-31" ) expect_type(spec, "list") @@ -244,12 +251,13 @@ test_that("get_2d_spectrum retrieves data successfully", { ) ) expect_s3_class(spec$forcings, "data.frame") - expect_shape(spec$forcings, dim = c(1416, 6)) + expect_shape(spec$forcings, dim = c(744, 6)) expect_equal(spec$station, "SEMREVO") }) test_that("get_2d_spectrum handles numeric node input", { # vcr::local_cassette("get_2d_spectrum_numeric_node") + skip_if_offline() spec_by_name <- get_2d_spectrum( "SEMREVO", @@ -268,21 +276,9 @@ test_that("get_2d_spectrum handles numeric node input", { expect_equal(spec_by_name$station, spec_by_index$station) }) -test_that("get_2d_spectrum handles character date inputs", { - # vcr::local_cassette("get_2d_spectrum_character_dates") - - spec <- get_2d_spectrum( - "SEMREVO", - start = "1994-01-01", - end = "1994-01-31" - ) - - expect_type(spec, "list") - expect_shape(spec$forcings, dim = c(744, 6)) -}) - test_that("get_2d_spectrum handles numeric date inputs", { # vcr::local_cassette("get_2d_spectrum_numeric_dates") + skip_if_offline() start_unix <- as.numeric(as.POSIXct("1994-01-01", tz = "UTC")) end_unix <- as.numeric(as.POSIXct("1994-01-31", tz = "UTC")) @@ -299,25 +295,16 @@ test_that("get_2d_spectrum handles numeric date inputs", { test_that("get_2d_spectrum handles multi-month requests", { # vcr::local_cassette("get_2d_spectrum_multi_month") + skip_if_offline() spec <- get_2d_spectrum( "SEMREVO", start = "1994-01-01", - end = "1994-03-31" + end = "1994-02-28" ) expect_type(spec, "list") - expect_gt(nrow(spec$forcings), 2000) -}) - -test_that("get_2d_spectrum validates forcings data structure", { - # vcr::local_cassette("get_2d_spectrum_forcings_structure") - - spec <- get_2d_spectrum( - "SEMREVO", - start = "1994-01-01", - end = "1994-01-31" - ) + expect_shape(spec$forcings, dim = c(1416, 6)) # Check forcings has expected columns expected_cols <- c("time", "dpt", "wnd", "wnddir", "cur", "curdir") @@ -325,18 +312,8 @@ test_that("get_2d_spectrum validates forcings data structure", { # Check time is POSIXct expect_s3_class(spec$forcings$time, "POSIXct") -}) - -test_that("get_2d_spectrum validates spectral array dimensions", { - # vcr::local_cassette("get_2d_spectrum_array_dimensions") - - spec <- get_2d_spectrum( - "SEMREVO", - start = "1994-01-01", - end = "1994-01-31" - ) - # Check that spectral array has correct dimensions + #Spectral data expect_true(is.array(spec$efth)) expect_equal(length(dim(spec$efth)), 3) expect_equal(dim(spec$efth)[1], length(spec$dir))