Nothing
dummy_email <- "test@example.com"
load_metno_fixture <- function() {
# locate canonical fixture under tests/fixtures
fp <- testthat::test_path("../fixtures/metno_forecast_compact.json")
cassette <- jsonlite::fromJSON(fp, simplifyVector = FALSE)
interaction <- cassette$http_interactions[[1]]
interaction$response$headers <- lapply(interaction$response$headers, identity)
interaction
}
mock_metno_call <- function(latitude = -27.5, longitude = 153.0, format = "compact") {
interaction <- load_metno_fixture()
body_string <- interaction$response$body$string
headers <- interaction$response$headers
status_code <- interaction$response$status
if (is.null(status_code)) status_code <- 200
temp_response <- list(
status_code = status_code,
parse = function(encoding = "UTF-8") body_string,
response_headers = headers
)
temp_client <- list(
get = function(query, headers = NULL) temp_response
)
# create a fake factory that returns our fake client
temp_factory <- function(endpoint, user_agent, timeout = 30) {
temp_client
}
# mock the package-internal factory so we don't bind into crul
testthat::with_mocked_bindings(
get_metno_forecast(
latitude = latitude,
longitude = longitude,
format = format,
api_key = dummy_email,
use_cache = FALSE
),
`.metno_http_client` = temp_factory,
.package = "weatherOz"
)
}
test_that("get_metno_forecast returns structured forecast with metadata (AWST)", {
forecast <- mock_metno_call()
expect_type(forecast, "list")
expect_s3_class(forecast$data, "data.table")
expect_true("time" %in% names(forecast$data))
expect_gt(nrow(forecast$data), 0)
# times should be in Australia/Perth
tz_attr <- attr(forecast$data$time, "tzone")
expect_true(!is.null(tz_attr) && tz_attr == "Australia/Perth")
expect_type(forecast$metadata, "list")
expect_true(!is.null(forecast$metadata$retrieved_at))
})
test_that("get_metno_forecast validates inputs", {
# latitude out of bounds
expect_error(get_metno_forecast(latitude = -100, longitude = 153, api_key = dummy_email))
# longitude out of bounds
expect_error(get_metno_forecast(latitude = -27.5, longitude = 300, api_key = dummy_email))
# invalid email
expect_error(get_metno_forecast(latitude = -27.5, longitude = 153, api_key = "invalid"))
})
test_that("metno_timeseries_to_data_table converts timeseries to data.table and AWST", {
interaction <- load_metno_fixture()
body_string <- interaction$response$body$string
parsed <- fromJSON(body_string, simplifyVector = FALSE)
ts <- parsed$properties$timeseries
dt_hourly <- metno_timeseries_to_data_table(ts)
expect_s3_class(dt_hourly, "data.table")
expect_true(nrow(dt_hourly) > 0)
expect_true(all(attr(dt_hourly$time, "tzone") == "Australia/Perth"))
})
test_that("metno_timeseries_to_data_table handles empty input", {
empty_dt <- metno_timeseries_to_data_table(list())
expect_equal(nrow(empty_dt), 0)
})
test_that("metno_get_dominant_symbol identifies dominant code", {
expect_equal(weatherOz:::metno_get_dominant_symbol(c()), NA_character_)
})
test_that("metno_resample_data_table aggregates to daily correctly", {
forecast <- mock_metno_call()
dt_hourly <- forecast$data
dt_daily <- metno_resample_data_table(dt_hourly, "daily")
expect_s3_class(dt_daily, "data.table")
expect_true("date" %in% names(dt_daily))
expect_true(inherits(dt_daily$date, "Date"))
})
test_that(".parse and .format metno http dates round trip", {
# load headers from fixture
interaction <- load_metno_fixture()
headers <- interaction$response$headers
# headers from VCR fixtures are lower-case; use 'expires' key
expires_raw <- headers$expires[[1]]
parsed <- weatherOz:::.parse_metno_http_date(expires_raw)
expect_true(inherits(parsed, "POSIXct"))
formatted <- weatherOz:::.format_metno_http_date(parsed)
expect_true(is.character(formatted))
expect_null(weatherOz:::.format_metno_http_date(NULL))
})
test_that("metno_timeseries_to_data_table converts timeseries to data.table", {
forecast <- mock_metno_call()
timeseries <- forecast$raw$properties$timeseries
dt_hourly <- weatherOz:::metno_timeseries_to_data_table(timeseries)
expect_s3_class(dt_hourly, "data.table")
expect_true("time" %in% names(dt_hourly))
expect_true(inherits(dt_hourly$time, "POSIXct"))
expect_true("air_temperature" %in% names(dt_hourly))
expect_true("precipitation_amount" %in% names(dt_hourly))
expect_true("symbol_code" %in% names(dt_hourly))
expect_true(all(lubridate::tz(dt_hourly$time) == "Australia/Perth")) # timezone aware (AWST)
})
test_that("metno_timeseries_to_data_table handles empty input", {
empty_dt <- weatherOz:::metno_timeseries_to_data_table(list())
expect_s3_class(empty_dt, "data.table")
expect_equal(nrow(empty_dt), 0)
})
test_that("metno_get_dominant_symbol identifies dominant code", {
expect_equal(weatherOz:::metno_get_dominant_symbol(c("clearsky_day", "fair_day", "partlycloudy_day")), "partlycloudy_day")
expect_equal(weatherOz:::metno_get_dominant_symbol(c("rain", "clearsky_day", "thunder")), "thunder")
expect_equal(weatherOz:::metno_get_dominant_symbol(c()), NA_character_)
})
test_that("metno_resample_data_table aggregates correctly", {
forecast <- mock_metno_call()
dt_hourly <- forecast$data
dt_daily <- weatherOz:::metno_resample_data_table(dt_hourly, "daily")
expect_s3_class(dt_daily, "data.table")
expect_true("date" %in% names(dt_daily))
expect_true("min_temperature" %in% names(dt_daily))
expect_lt(nrow(dt_daily), nrow(dt_hourly))
dt_hourly_again <- weatherOz:::metno_resample_data_table(dt_hourly, "hourly")
expect_s3_class(dt_hourly_again, "data.table")
expect_equal(dt_hourly_again$date, dt_hourly$time)
})
test_that("get_metno_daily_forecast aggregates daily data", {
mock_result <- mock_metno_call()
with_mocked_bindings(
{
daily <- get_metno_daily_forecast(
latitude = -27.5,
longitude = 153.0,
days = 5,
api_key = dummy_email
)
expect_s3_class(daily, "data.table")
expect_equal(nrow(daily), 5)
expect_true("date" %in% names(daily))
},
get_metno_forecast = function(...) mock_result,
.package = "weatherOz"
)
})
test_that("get_metno_forecast reuses cached forecast when fresh", {
cache_dir <- withr::local_tempdir()
vcr::use_cassette("metno_cache_reuse_compact", {
skip_if_offline()
first <- get_metno_forecast(
latitude = -27.5,
longitude = 153.0,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
second <- get_metno_forecast(
latitude = -27.5,
longitude = 153.0,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
})
expect_identical(first$metadata$cache$source, "network")
expect_identical(second$metadata$cache$source, "cache")
expect_identical(second$metadata$cache$used_if_modified_since, FALSE)
})
test_that("get_metno_forecast sends conditional revalidation for stale cache", {
cache_dir <- withr::local_tempdir()
vcr::use_cassette("metno_cache_revalidate_compact", {
skip_if_offline()
first <- get_metno_forecast(
latitude = -27.5,
longitude = 153.0,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
cache_path <- first$metadata$cache$cache_path
expect_true(file.exists(cache_path))
cached <- readRDS(cache_path)
cached$metadata$expires <- lubridate::now(tzone = "Australia/Perth") - lubridate::minutes(1)
saveRDS(cached, file = cache_path)
second <- get_metno_forecast(
latitude = -27.5,
longitude = 153.0,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
})
expect_identical(second$metadata$cache$source, "revalidated")
expect_identical(second$metadata$cache$used_if_modified_since, TRUE)
})
test_that("get_metno_daily_forecast reuses hourly session cache", {
cache_dir <- withr::local_tempdir()
vcr::use_cassette("metno_daily_cache_reuse_compact", {
skip_if_offline()
get_metno_forecast(
latitude = -27.5,
longitude = 153.0,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
first <- get_metno_daily_forecast(
latitude = -27.5,
longitude = 153.0,
days = 3,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
second <- get_metno_daily_forecast(
latitude = -27.5,
longitude = 153.0,
days = 3,
api_key = dummy_email,
use_cache = TRUE,
cache_dir = cache_dir
)
})
expect_s3_class(first, "data.table")
expect_s3_class(second, "data.table")
expect_equal(nrow(first), 3)
expect_equal(nrow(second), 3)
})
test_that(".parse and .format metno http dates round trip", {
timestamp <- "Wed, 29 Oct 2025 01:43:37 GMT"
parsed <- weatherOz:::.parse_metno_http_date(timestamp)
expect_true(inherits(parsed, "POSIXct"))
expect_equal(weatherOz:::.format_metno_http_date(parsed), timestamp)
expect_null(weatherOz:::.parse_metno_http_date(NULL))
expect_null(weatherOz:::.format_metno_http_date(NULL))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.