diff --git a/DESCRIPTION b/DESCRIPTION index ae971bd6..6ddab734 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,6 +33,7 @@ Imports: duckdb, EpiNow2 (>= 1.4.0), jsonlite, + jsonvalidate, rlang, rstan, tidybayes diff --git a/Makefile b/Makefile index 4c1d6be6..c015d3e4 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,8 @@ test: document: Rscript -e "roxygen2::roxygenize()" + git add NAMESPACE + git add man/ check: Rscript -e "rcmdcheck::rcmdcheck()" diff --git a/NAMESPACE b/NAMESPACE index 542cc5f3..060fc551 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ export(apply_exclusions) export(download_from_azure_blob) export(extract_diagnostics) export(fetch_blob_container) +export(fetch_config) export(fetch_credential_from_env_var) export(fit_model) export(format_delay_interval) @@ -16,5 +17,6 @@ export(read_data) export(read_disease_parameters) export(read_exclusions) export(read_interval_pmf) +export(validate_config) export(write_model_outputs) export(write_output_dir_structure) diff --git a/NEWS.md b/NEWS.md index 7cbda965..9518e0ff 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,11 @@ # CFAEpiNow2Pipeline (development version) + +* Config reader with schema validation and enforcement +* Merges workflows 1 and 2 into a single workflow. +* Now uses CFA Azure ACR and images in the workflows and Dockerfiles, etc. +* Added Docker image with all the requirements to build the package. +* Bump pre-commit hooks +* Fix bug in warning message for incomplete data read (h/t @damonbayer) * Merges workflows 1 and 2 into a single workflow. * Now uses CFA Azure ACR and images in the workflows and Dockerfiles, etc. * Added Docker image with all the requirements to build the package. @@ -23,4 +30,9 @@ * Write outputs to file * Specify number of samples draws with `iter_sampling` * Fix NOTE from missing variable name used with NSE +* Fix bugs in date casting caused by DuckDB v1.1.1 release +* Drop unused pre-commit hooks +* Write outputs to file +* Specify number of samples draws with `iter_sampling` +* Fix NOTE from missing variable name used with NSE * Read from new parameters schema diff --git a/R/config.R b/R/config.R new file mode 100644 index 00000000..1d84729c --- /dev/null +++ b/R/config.R @@ -0,0 +1,100 @@ +#' Fetch the config from an external resource +#' +#' This step is the first part of the modeling pipeline. It looks to Azure Blob +#' and downloads the Rt model run's config to the local config (if +#' `blob_storage_container` is specified), reads the config in from the +#' filesystem, and validates that it matches expectations. If any of these steps +#' fail, the pipeline fails with an informative error message. Note, however, +#' that a failure in this initial step suggests that something fundamental is +#' misspecified and the logs will likely not be preserved in a Blob Container if +#' running in Azure. +#' +#' The validation relies on `inst/data/config_schema.json` for validation. This +#' file is in `json-schema` notation and generated programatically via +#' https://www.jsonschema.net/. +#' +#' @param config_path The path to the config file, either in the local +#' filesystem or with an Azure Blob Storage container. If +#' `blob_storage_container` is specified, the path is assumed to be within +#' the specified container otherwise it is assumed to be in the local +#' filesystem. +#' @param local_dest The local directory to write the config to when downloading +#' it from `blob_storage_container`. This argument is ignored unless +#' `blob_storage_container` is specified. +#' @param blob_storage_container The storage container holding the config at +#' `config_path` +#' @param config_schema_path The path to the file holding the schema for the +#' config json for the validator to use. +#' +#' @return A list of lists, the config for the run. +#' @export +fetch_config <- function( + config_path, + local_dest, + blob_storage_container, + config_schema_path = system.file("extdata/config_schema.json", + package = "CFAEpiNow2Pipeline" + )) { + if (!rlang::is_null(blob_storage_container)) { + download_from_azure_blob( + config_path, + local_dest, + container_name = blob_storage_container + ) + } else { + cli::cli_alert( + "No blob storage container provided. Reading from local path." + ) + } + + cli::cli_alert_info("Loading config from {.path {config_path}}") + validate_config(config_path, config_schema_path) + + config <- rlang::try_fetch( + jsonlite::read_json(config_path), + error = function(con) { + cli::cli_abort( + "Error loading config from {.path {config_path}}", + parent = con, + class = "CFA_Rt" + ) + } + ) + + return(config) +} + +#' Compare loaded JSON against expectation in `inst/data/config-schema.json` +#' +#' @inheritParams fetch_config +#' @return NULL, invisibly +#' @export +validate_config <- function( + config_path, + config_schema_path = system.file("extdata/config_schema.json", + package = "CFAEpiNow2Pipeline" + )) { + is_config_valid <- rlang::try_fetch( + jsonvalidate::json_validate( + json = config_path, + schema = config_schema_path, + engine = "ajv", + verbose = TRUE, + greedy = TRUE, + error = TRUE + ), + error = function(con) { + cli::cli_abort( + c( + "Error while validating config", + "!" = "Config path: {.path {config_path}}", + "!" = "Schema path: {.path {config_schema_path}}" + ), + parent = con, + class = "CFA_Rt" + ) + } + ) + + invisible(is_config_valid) +} diff --git a/R/es b/R/es new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md index f0e42e9a..a079c8f7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,231 @@ This package implements functions for: 1. **Logging**: Steps in the pipeline have comprehensive R-style logging, with the the [cli](https://github.com/r-lib/cli) package 1. **Metadata**: Extract comprehensive metadata on the model run and store alongside outputs +## Configuration Specification + +This section provides a detailed description of the configuration parameters used in the application. +They should be provided to the pipeline in JSON format, with all keys below _required_. + +### Overview + +The configuration is represented in JSON format as follows: + +```json +{ + "job_id": "6183da58-89bc-455f-8562-4f607257a876", + "task_id": "bc0c3eb3-7158-4631-a2a9-86b97357f97e", + "as_of_date": "2023-01-01", + "disease": "test", + "geo_value": ["test"], + "geo_type": "test", + "parameters": { + "path": "data/parameters.parquet", + "blob_storage_container": null + }, + "data": { + "path": "gold/", + "blob_storage_container": null, + "report_date": [ + "2023-01-01" + ], + "reference_date": [ + "2023-01-01", + "2022-12-30", + "2022-12-29" + ] + }, + "seed": 42, + "horizon": 14, + "priors": { + "rt": { + "mean": 1.0, + "sd": 0.2 + }, + "gp": { + "alpha_sd": 0.01 + } + }, + "sampler_opts": { + "cores": 4, + "chains": 4, + "adapt_delta": 0.99, + "max_treedepth": 12 + } +} +``` + +### Parameter Descriptions + +#### `job_id` + +- **Type**: `String` +- **Description**: A unique identifier for the job. + +#### `task_id` + +- **Type**: `String` +- **Description**: A unique identifier for the task within the job. See [Azure Batch for documentation](https://learn.microsoft.com/en-us/azure/batch/jobs-and-tasks) of the task vs. job abstraction. + +#### `as_of_date` + +- **Type**: `String` (Date in `YYYY-MM-DD` format) +- **Description**: Use the parameters that were used in production on this date. Set for the current date for the most up-to-to date version of the parameters and set to an earlier date to use parameters from an earlier time period. + +#### `disease` + +- **Type**: `String` +- **Description**: The name of the disease being modeled. One of `COVID-19`, `Influenza`, or `test`. + +#### `geo_value` + +- **Type**: `Array[String]` +- **Description**: A [FIPS Alpha code](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code) identifying a state or territory. This code is the standard uppercase two-letter state abbreviation. It can also be `US` for an aggregate national estimate. + +#### `geo_type` + +- **Type**: `String` +- **Description**: The type of geographical area (e.g., `"state"`, `"county"`). + +#### `parameters` + +An object containing paths to parameter files. + +- **`path`** + - **Type**: `String` + - **Description**: File path to the parameters file in Parquet format. +- **`blob_storage_container`** + - **Type**: `String` or `null` + - **Description**: Name of the blob storage container, if applicable. + +#### `data` + +An object containing data paths and dates. + +- **`path`** + - **Type**: `String` + - **Description**: Directory path to the data files. +- **`blob_storage_container`** + - **Type**: `String` or `null` + - **Description**: Name of the blob storage container, if applicable. +- **`report_date`** + - **Type**: `Array[String]` (Dates in `YYYY-MM-DD` format) + - **Description**: List of report dates to include. +- **`reference_date`** + - **Type**: `Array[String]` (Dates in `YYYY-MM-DD` format) + - **Description**: List of reference dates to include. + +#### `seed` + +- **Type**: `Integer` +- **Description**: Random seed for reproducibility. + +#### `horizon` + +- **Type**: `Integer` +- **Description**: Forecast horizon in days. Must be a natural number. + +#### `priors` + +An object specifying prior distributions for model parameters. See the [EpiNow2](https://epiforecasts.io/EpiNow2/articles/estimate_infections.html) model definition for more information. + +- **`rt`**: Prior settings for the reproduction number \( R_t \). + - **`mean`** + - **Type**: `Float` + - **Description**: Mean of the prior distribution for \( R_t \). + - **`sd`** + - **Type**: `Float` + - **Description**: Standard deviation of the prior distribution for \( R_t \). +- **`gp`**: Prior settings for the latent Gaussian process of Rt. + - **`alpha_sd`** + - **Type**: `Float` + - **Description**: Standard deviation for the alpha parameter in the Gaussian process. A larger standard deviation implies more wiggliness in the Rt estimate. + +#### `sampler_opts` + +An object containing options for the Stan HMC algorithm. + +- **`cores`** + - **Type**: `Integer` + - **Description**: Number of CPU cores to utilize. +- **`chains`** + - **Type**: `Integer` + - **Description**: Number of Markov chains to run. Should be greater than or equal to the number of cores. +- **`adapt_delta`** + - **Type**: `Float` + - **Description**: Target acceptance probability for the sampler's adaptation phase. +- **`max_treedepth`** + - **Type**: `Integer` + - **Description**: Log of the number of evaluations allowed before termination for non-convergence. + +--- + +**Note**: All date strings should follow the `YYYY-MM-DD` format to ensure consistency and proper parsing. + +## Output format + +The end goals of this package is to standardize the raw outputs from EpiNow2 into samples and summaries tables, and to write those standardized outputs, along with relevant metadata, logs, etc. to a standard directory structure. Once in CFA's standard format, the outputs can be passed into a separate pipeline that handles post-processing (e.g. plotting, scoring, analysis) of Rt estimates from several different Rt estimation models. + +### Directories + +The nested partitioning structure of the outputs is designed to facilitate both automated processes and manual investigation: files are organized by job and task IDs, allowing for efficient file operations using glob patterns, while also maintaining a clear hierarchy that aids human users in navigating to specific results or logs. Files meant primarily for machine-readable consumption (i.e., draws, summaries, diagnostics) are structured together to make globbing easier. Files meant primarily for human investigation (i.e., logs, model fit object) are grouped together by task to facilitate manual workflows. +In this workflow, task IDs correspond to location specific model runs (which are independent of one another) and the jobid refers to a unique model run and disease. For example, a production job should contain task IDs for each of the 50 states and the US, but a job submitted for testing or experimentation might contain a smaller number of tasks/locations. + +```bash +/ +├── job_/ +│ ├── raw_samples/ +│ │ ├── samples_.parquet +│ ├── summarized_quantiles/ +│ │ ├── summarized_.parquet +│ ├── diagnostics/ +│ │ ├── diagnostics_.parquet +│ ├── tasks/ +│ │ ├── task_/ +│ │ │ ├── model.rds +│ │ │ ├── metadata.json +│ │ │ ├── stdout.log +│ │ │ └── stderr.log +│ ├── job_metadata.json +``` + +`/`: The base output directory. This could, for example, be `/` in a Docker container or dedicated output directory. +- `job_/`: A directory named after the specific job identifier, containing all outputs related to that job. All tasks within a job share this same top-level directory. + - `raw_samples/`: A subdirectory within each job folder that holds the raw sample files from all tasks in the job. Task-specific *draws* output files all live together in this directory to enable easy globbing over task-partitioned outputs. + - `samples_.parquet`: A file containing raw samples from the model, associated with a particular task identifier. This file has columns `job_id`, `task_id`, `geo_value`, `disease`, `model`, `_draw`, `_chain`, `_iteration`, `_variable`, `value`, and `reference_date`. These variables follow the [{tidybayes}](https://mjskay.github.io/tidybayes/articles/tidybayes.html) specification. + - `summarized_quantiles/`: A subdirectory for storing summarized quantile data. Task-specific *summarized* output files all live together in this directory to enable easy globbing over task-partitioned outputs. + - `summarized_.parquet`: A file with summarized quantiles relevant to a specific task identifier. This file has columns `job_id`, `task_id`, `geo_value`, `disease`, `model`, `value`, `_lower`, `_upper`, `_width`, `_point`, `_interval`, and `reference_date`. These variables follow the [{tidybayes}](https://mjskay.github.io/tidybayes/articles/tidybayes.html) specification. + - `diagnostics/`: A subdirectory for storing model fit diagnostics. Task-specific *diagnostic* output files all live together in this directory to enable easy globbing over task-partitioned outputs. + - `diagnostic_.parquet`: A file with diagnostics relevant to a specific task identifier. This file has columns `diagnostic`, `value`, `job_id`, `task_id`, `geo_value`, `disease`, and `model`. + - `tasks/`: This directory contains subdirectories for each task within a job. These are files that are less likely to require globbing from the data lake than manual investigation, so are stored togehter. + - `task_/`: Each task has its own folder identified by the task ID, which includes several files: + - `model.rds`: An RDS file storing the EpiNow2 model object fit to the data. + - `metadata.json`: A JSON file containing additional metadata about the model run for this task. + - `stdout.log`: A log file capturing standard output from the model run process. + - `stderr.log`: A log file capturing standard error output from the model run process. +- `job_metadata.json`: A JSON file located in the root of each job's directory, providing metadata about the entire job. + +### Model-estimated quantities + +EpiNow2 estimates the incident cases $\hat y_{td}$ for timepoint $t \in \{1, ..., T\}$ and delay $d \in \{1, ..., D\}$ where $D \le T$. In the single vintage we're providing to EpiNow2, the delay $d$ moves inversely to timepoints, so $d = T - t + 1$. + +The observed data vector of length $T$ is $y_{td} \in W$. We supply a nowcasting correction PMF $\nu$ for the last $D$ timepoints where $\nu_d \in [0, 1],$ and $\sum_{d=1}^D\nu_d = 1$. We also have some priors $\Theta$. + +We use EpiNow2's generative model $f(y, \nu, \Theta)$. + +EpiNow2 is a forward model that produces an expected nowcasted case count for each $t$ and $d$ pair: $\hat \gamma_{td}$. + It applies the nowcasting correction $\nu$ to the last $D$ timepoints of $\hat \gamma$ to produce the expected right-truncated case count $\hat y$. Note that these _expected_ case counts (with and without right-truncation) don't have observation noise included. + +We can apply negative binomial observation noise using EpiNow2's estimate of the negative binomial overdispersion parameter $\hat \phi$ and the expected case counts. The posterior predictive distributions of nowcasted case counts is $\tilde \gamma \sim \text{NB}(\hat \gamma, \hat \phi)$. The posterior predicted right-truncated case count is $\tilde y \sim \text{NB}(\hat y, \hat \phi)$. + +We can get 3 of these 4 quantities pre-generated from the returned EpiNow2 Stan model: + +- $\hat \gamma$: The expected nowcasted case count is `reports[t]` +- $\hat y$: The expected right-truncated case count is `obs_reports[t]` +- $\tilde \gamma$: The posterior-predicted nowcasted case count is `imputed_reports[t]` +- $\tilde y$: The posterior-predicted right-truncated case count isn't returned by EpiNow2. + +We also save the $R_t$ estimate at time $t$ and the intrinsic growth rate at time $t$. + ## Output format The end goals of this package is to standardize the raw outputs from EpiNow2 into samples and summaries tables, and to write those standardized outputs, along with relevant metadata, logs, etc. to a standard directory structure. Once in CFA's standard format, the outputs can be passed into a separate pipeline that handles post-processing (e.g. plotting, scoring, analysis) of Rt estimates from several different Rt estimation models. diff --git a/inst/extdata/config_schema.json b/inst/extdata/config_schema.json new file mode 100644 index 00000000..faf85515 --- /dev/null +++ b/inst/extdata/config_schema.json @@ -0,0 +1,191 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/Epinow2", + "definitions": { + "Epinow2": { + "type": "object", + "additionalProperties": false, + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "task_id": { + "type": "string", + "format": "uuid" + }, + "as_of_date": { + "type": "string", + "format": "date" + }, + "disease": { + "type": "string" + }, + "geo_value": { + "type": "array", + "items": { + "type": "string" + } + }, + "geo_type": { + "type": "string" + }, + "parameters": { + "$ref": "#/definitions/Parameters" + }, + "data": { + "$ref": "#/definitions/Data" + }, + "seed": { + "type": "integer" + }, + "horizon_days": { + "type": "integer" + }, + "priors": { + "$ref": "#/definitions/Priors" + }, + "sampler_opts": { + "$ref": "#/definitions/SamplerOpts" + } + }, + "required": [ + "as_of_date", + "data", + "disease", + "geo_type", + "geo_value", + "horizon", + "job_id", + "parameters", + "priors", + "sampler_opts", + "seed", + "task_id" + ], + "title": "Epinow2" + }, + "Data": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "blob_storage_container": { + "type": ["null", "string"] + }, + "report_date": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + "reference_date": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + } + }, + "required": [ + "blob_storage_container", + "path", + "reference_date", + "report_date" + ], + "title": "Data" + }, + "Parameters": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "blob_storage_container": { + "type": ["string", "null"] + } + }, + "required": [ + "blob_storage_container", + "path" + ], + "title": "Parameters" + }, + "Priors": { + "type": "object", + "additionalProperties": false, + "properties": { + "rt": { + "$ref": "#/definitions/Rt" + }, + "gp": { + "$ref": "#/definitions/Gp" + } + }, + "required": [ + "gp", + "rt" + ], + "title": "Priors" + }, + "Gp": { + "type": "object", + "additionalProperties": false, + "properties": { + "alpha_sd": { + "type": "number" + } + }, + "required": [ + "alpha_sd" + ], + "title": "Gp" + }, + "Rt": { + "type": "object", + "additionalProperties": false, + "properties": { + "mean": { + "type": "integer" + }, + "sd": { + "type": "number" + } + }, + "required": [ + "mean", + "sd" + ], + "title": "Rt" + }, + "SamplerOpts": { + "type": "object", + "additionalProperties": false, + "properties": { + "cores": { + "type": "integer" + }, + "chains": { + "type": "integer" + }, + "adapt_delta": { + "type": "number" + }, + "max_treedepth": { + "type": "integer" + } + }, + "required": [ + "adapt_delta", + "chains", + "cores", + "max_treedepth" + ], + "title": "SamplerOpts" + } + } +} diff --git a/man/fetch_config.Rd b/man/fetch_config.Rd new file mode 100644 index 00000000..4bbb33c0 --- /dev/null +++ b/man/fetch_config.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{fetch_config} +\alias{fetch_config} +\title{Fetch the config from an external resource} +\usage{ +fetch_config( + config_path, + local_dest, + blob_storage_container, + config_schema_path = system.file("extdata/config_schema.json", package = + "CFAEpiNow2Pipeline") +) +} +\arguments{ +\item{config_path}{The path to the config file, either in the local +filesystem or with an Azure Blob Storage container. If +\code{blob_storage_container} is specified, the path is assumed to be within +the specified container otherwise it is assumed to be in the local +filesystem.} + +\item{local_dest}{The local directory to write the config to when downloading +it from \code{blob_storage_container}. This argument is ignored unless +\code{blob_storage_container} is specified.} + +\item{blob_storage_container}{The storage container holding the config at +\code{config_path}} + +\item{config_schema_path}{The path to the file holding the schema for the +config json for the validator to use.} +} +\value{ +A list of lists, the config for the run. +} +\description{ +This step is the first part of the modeling pipeline. It looks to Azure Blob +and downloads the Rt model run's config to the local config (if +\code{blob_storage_container} is specified), reads the config in from the +filesystem, and validates that it matches expectations. If any of these steps +fail, the pipeline fails with an informative error message. Note, however, +that a failure in this initial step suggests that something fundamental is +misspecified and the logs will likely not be preserved in a Blob Container if +running in Azure. +} +\details{ +The validation relies on \code{inst/data/config_schema.json} for validation. This +file is in \code{json-schema} notation and generated programatically via +https://www.jsonschema.net/. +} diff --git a/man/validate_config.Rd b/man/validate_config.Rd new file mode 100644 index 00000000..da3e7608 --- /dev/null +++ b/man/validate_config.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{validate_config} +\alias{validate_config} +\title{Compare loaded JSON against expectation in \code{inst/data/config-schema.json}} +\usage{ +validate_config( + config_path, + config_schema_path = system.file("extdata/config_schema.json", package = + "CFAEpiNow2Pipeline") +) +} +\arguments{ +\item{config_path}{The path to the config file, either in the local +filesystem or with an Azure Blob Storage container. If +\code{blob_storage_container} is specified, the path is assumed to be within +the specified container otherwise it is assumed to be in the local +filesystem.} + +\item{config_schema_path}{The path to the file holding the schema for the +config json for the validator to use.} +} +\value{ +NULL, invisibly +} +\description{ +Compare loaded JSON against expectation in \code{inst/data/config-schema.json} +} diff --git a/tests/testthat/data/bad_sample_config.json b/tests/testthat/data/bad_sample_config.json new file mode 100644 index 00000000..c435d090 --- /dev/null +++ b/tests/testthat/data/bad_sample_config.json @@ -0,0 +1,32 @@ + +{ + "as_of_date": "2023-01-01", + "geo_value": "test", + "geo_type": "test", + "report_date": [ + "01-01" + ], + "reference_date": [ + "2023-01-01", + "2022-12-30", + "2022-12-29" + ], + "seed": "abc", + "horizon": 14, + "priors": { + "rt": { + "mean": 1.0, + "sd": 0.2 + }, + "gp": { + "alpha_sd": 0.01 + } + }, + "sampler_opts": { + "cores": 4, + "chains": 4, + "adapt_delta": 0.99, + "max_treedepth": 12, + "not_a_parameter": -12 + } +} diff --git a/tests/testthat/data/sample_config.json b/tests/testthat/data/sample_config.json new file mode 100644 index 00000000..a57f98c8 --- /dev/null +++ b/tests/testthat/data/sample_config.json @@ -0,0 +1,41 @@ +{ + "job_id": "6183da58-89bc-455f-8562-4f607257a876", + "task_id": "bc0c3eb3-7158-4631-a2a9-86b97357f97e", + "as_of_date": "2023-01-01", + "disease": "test", + "geo_value": ["test"], + "geo_type": "test", + "parameters": { + "path": "data/parameters.parquet", + "blob_storage_container": null + }, + "data": { + "path": "gold/", + "blob_storage_container": null, + "report_date": [ + "2023-01-01" + ], + "reference_date": [ + "2023-01-01", + "2022-12-30", + "2022-12-29" + ] + }, + "seed": 42, + "horizon": 14, + "priors": { + "rt": { + "mean": 1.0, + "sd": 0.2 + }, + "gp": { + "alpha_sd": 0.01 + } + }, + "sampler_opts": { + "cores": 4, + "chains": 4, + "adapt_delta": 0.99, + "max_treedepth": 12 + } +} diff --git a/tests/testthat/helper-write_parameter_file.R b/tests/testthat/helper-write_parameter_file.R index be57727b..c0639c8f 100644 --- a/tests/testthat/helper-write_parameter_file.R +++ b/tests/testthat/helper-write_parameter_file.R @@ -1,6 +1,5 @@ write_sample_parameters_file <- function(value, path, - state, param, disease, parameter, diff --git a/tests/testthat/test-fetch_config.R b/tests/testthat/test-fetch_config.R new file mode 100644 index 00000000..d69bf9e5 --- /dev/null +++ b/tests/testthat/test-fetch_config.R @@ -0,0 +1,44 @@ +test_that("Test config loads", { + config_path <- test_path("data/sample_config.json") + + expected <- jsonlite::read_json(config_path) + actual <- fetch_config( + config_path = config_path, + local_dest = NULL, + blob_storage_container = NULL + ) + + expect_equal(actual, expected) +}) + +test_that("Bad config errors", { + config_path <- test_path("data/bad_sample_config.json") + + expect_error( + { + fetch_config( + config_path = config_path, + local_dest = NULL, + blob_storage_container = NULL + ) + }, + class = "CFA_Rt" + ) +}) + +test_that("Test config validates", { + config_path <- test_path("data/sample_config.json") + + expect_true( + validate_config( + config_path + ) + ) +}) + + +test_that("Bad config errors", { + config_path <- test_path("data/bad_sample_config.json") + + expect_error(validate_config(config_path)) +})