From 8270cb74745b7112784d398f6765074451cf8bf8 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 10:42:05 +0100 Subject: [PATCH 01/44] Refactor test harness structure --- dotenv/tests/integration/dotenv.rs | 11 +++++++++++ dotenv/tests/integration/main.rs | 5 ++++- dotenv/tests/integration/util/mod.rs | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 dotenv/tests/integration/dotenv.rs diff --git a/dotenv/tests/integration/dotenv.rs b/dotenv/tests/integration/dotenv.rs new file mode 100644 index 00000000..a763659c --- /dev/null +++ b/dotenv/tests/integration/dotenv.rs @@ -0,0 +1,11 @@ +use crate::util::*; + +#[test] +fn dotenv_ok_default_env() { + test_in_default_env(|| { + dotenvy::dotenv().ok(); + assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); + assert_env_var(TEST_KEY, TEST_VALUE); + }); +} + diff --git a/dotenv/tests/integration/main.rs b/dotenv/tests/integration/main.rs index 83c8c0aa..abb4e03a 100644 --- a/dotenv/tests/integration/main.rs +++ b/dotenv/tests/integration/main.rs @@ -1 +1,4 @@ -mod util; +pub mod util; + +mod dotenv; + diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index 12430b63..cae03caf 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -1,8 +1,8 @@ -#![allow(dead_code)] +use std::env::{self, VarError}; mod testenv; -use std::env::{self, VarError}; +pub use testenv::{test_in_default_env, test_in_env, KeyVal, TestEnv}; /// Default key used in envfile pub const TEST_KEY: &str = "TESTKEY"; From 23c6f9e2e103d512cceab563909ea6b9a383e2bf Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 10:43:46 +0100 Subject: [PATCH 02/44] Test the test harness --- dotenv/tests/integration/util/mod.rs | 1 + dotenv/tests/integration/util/tests.rs | 95 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 dotenv/tests/integration/util/tests.rs diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index cae03caf..f77d5605 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -1,6 +1,7 @@ use std::env::{self, VarError}; mod testenv; +mod tests; pub use testenv::{test_in_default_env, test_in_env, KeyVal, TestEnv}; diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs new file mode 100644 index 00000000..a5d21c7b --- /dev/null +++ b/dotenv/tests/integration/util/tests.rs @@ -0,0 +1,95 @@ +use super::*; + +mod default_env { + use super::*; + + #[test] + fn vars_state() { + test_in_default_env(|| { + assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); + assert_env_var_unset(TEST_KEY); + }); + } + + #[test] + fn envfile_exists() { + let testenv = TestEnv::default(); + assert_envfile_exists_in_testenv(testenv); + } + + + #[test] + fn envfile_loaded_vars_state() { + test_in_default_env(|| { + dotenvy::dotenv().expect("Default TestEnv should have .env file"); + // dotenv() does not override existing var + assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); + assert_env_var(TEST_KEY, TEST_VALUE); + }); + } +} + +mod testenv_init { + use super::*; + + #[test] + fn vars_state() { + let init_testenv = TestEnv::init(); + assert_default_keys_not_set_in_testenv(init_testenv); + } + + #[test] + fn no_envfile() { + let init_testenv = TestEnv::init(); + let envfile_path = init_testenv.envfile_path().to_owned(); + + test_in_env(init_testenv, || { + assert!(!envfile_path.exists()); + assert!(dotenvy::dotenv().is_err()); + }); + } +} + +mod testenv_init_with_envfile { + use super::*; + + #[test] + fn default_envfile_vars_state() { + let testenv = TestEnv::init_with_envfile(create_default_envfile()); + assert_default_keys_not_set_in_testenv(testenv); + } + + #[test] + fn default_envfile_exists() { + let testenv = TestEnv::init_with_envfile(create_default_envfile()); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn default_envfile_loaded_vars_state() { + let init_testenv = TestEnv::init_with_envfile(create_default_envfile()); + test_in_env(init_testenv, || { + dotenvy::dotenv().expect("Default TestEnv should have .env file"); + // dotenv() does not override existing var + // but existing key is not set in this testenv + assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); + assert_env_var(TEST_KEY, TEST_VALUE); + }); + } +} + +fn assert_envfile_exists_in_testenv(testenv: TestEnv) { + let envfile_path = testenv.envfile_path().to_owned(); + + test_in_env(testenv, || { + assert!(envfile_path.exists()); + assert!(dotenvy::dotenv().is_ok()); + }); +} + +fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { + test_in_env(testenv, || { + assert_env_var_unset(TEST_EXISTING_KEY); + assert_env_var_unset(TEST_KEY); + }); +} From 726c0571ce3bc8e23731cb09f55811aeb465a4e0 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 11:07:47 +0100 Subject: [PATCH 03/44] Rustfmt test harness --- dotenv/tests/integration/dotenv.rs | 1 - dotenv/tests/integration/main.rs | 1 - dotenv/tests/integration/util/tests.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/dotenv/tests/integration/dotenv.rs b/dotenv/tests/integration/dotenv.rs index a763659c..966fbf23 100644 --- a/dotenv/tests/integration/dotenv.rs +++ b/dotenv/tests/integration/dotenv.rs @@ -8,4 +8,3 @@ fn dotenv_ok_default_env() { assert_env_var(TEST_KEY, TEST_VALUE); }); } - diff --git a/dotenv/tests/integration/main.rs b/dotenv/tests/integration/main.rs index abb4e03a..a61ebe72 100644 --- a/dotenv/tests/integration/main.rs +++ b/dotenv/tests/integration/main.rs @@ -1,4 +1,3 @@ pub mod util; mod dotenv; - diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index a5d21c7b..fe8f60a2 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -17,7 +17,6 @@ mod default_env { assert_envfile_exists_in_testenv(testenv); } - #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { From 588959d27855252be55450cf5d105b07bd341d2f Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 11:13:04 +0100 Subject: [PATCH 04/44] Refactor dotenv usage into wrapper --- dotenv/tests/integration/util/tests.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index fe8f60a2..02ba6792 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use super::*; mod default_env { @@ -20,7 +22,7 @@ mod default_env { #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { - dotenvy::dotenv().expect("Default TestEnv should have .env file"); + dotenv_wrapper().expect("Default TestEnv should have .env file"); // dotenv() does not override existing var assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); assert_env_var(TEST_KEY, TEST_VALUE); @@ -44,7 +46,7 @@ mod testenv_init { test_in_env(init_testenv, || { assert!(!envfile_path.exists()); - assert!(dotenvy::dotenv().is_err()); + assert!(dotenv_wrapper().is_err()); }); } } @@ -68,7 +70,7 @@ mod testenv_init_with_envfile { fn default_envfile_loaded_vars_state() { let init_testenv = TestEnv::init_with_envfile(create_default_envfile()); test_in_env(init_testenv, || { - dotenvy::dotenv().expect("Default TestEnv should have .env file"); + dotenv_wrapper().expect("Default TestEnv should have .env file"); // dotenv() does not override existing var // but existing key is not set in this testenv assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); @@ -82,7 +84,7 @@ fn assert_envfile_exists_in_testenv(testenv: TestEnv) { test_in_env(testenv, || { assert!(envfile_path.exists()); - assert!(dotenvy::dotenv().is_ok()); + assert!(dotenv_wrapper().is_ok()); }); } @@ -92,3 +94,7 @@ fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { assert_env_var_unset(TEST_KEY); }); } + +fn dotenv_wrapper() -> dotenvy::Result { + dotenvy::dotenv() +} From 48331425cc728844bad4fa03b3cb81bb34daf127 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 11:57:52 +0100 Subject: [PATCH 05/44] Fix envfile contents now represented as bytes --- dotenv/tests/integration/util/testenv.rs | 49 +++++++++++++----------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/dotenv/tests/integration/util/testenv.rs b/dotenv/tests/integration/util/testenv.rs index a3d251bc..f085f2f4 100644 --- a/dotenv/tests/integration/util/testenv.rs +++ b/dotenv/tests/integration/util/testenv.rs @@ -31,7 +31,7 @@ pub struct TestEnv { temp_dir: TempDir, work_dir: PathBuf, env_vars: Vec, - envfile_contents: Option, + envfile_contents: Option>, envfile_path: PathBuf, } @@ -109,22 +109,12 @@ impl TestEnv { /// /// No pre-existing env_vars set. The envfile_name is set to `.env`. The /// working directory is the created temporary directory. - pub fn init_with_envfile(contents: impl ToString) -> Self { + pub fn init_with_envfile(contents: impl Into>) -> Self { let mut test_env = Self::init(); test_env.set_envfile_contents(contents); test_env } - /// Change the name of the default `.env` file. - /// - /// It will still be placed in the root temporary directory. If you need to - /// put the envfile in a different directory, use - /// [`set_envfile_path`](TestEnv::set_envfile_path) instead. - pub fn set_envfile_name(&mut self, name: impl AsRef) -> &mut Self { - self.envfile_path = self.temp_path().join(name); - self - } - /// Change the absolute path to the envfile. pub fn set_envfile_path(&mut self, path: PathBuf) -> &mut Self { self.envfile_path = path; @@ -134,11 +124,12 @@ impl TestEnv { /// Specify the contents of the envfile. /// /// If this is the only change to the [`TestEnv`] being made, use - /// [`new_with_envfile`](TestEnv::new_with_envfile). + /// [`init_with_envfile`](TestEnv::init_with_envfile). /// /// Setting it to an empty string will cause an empty envfile to be created - pub fn set_envfile_contents(&mut self, contents: impl ToString) -> &mut Self { - self.envfile_contents = Some(contents.to_string()); + pub fn set_envfile_contents(&mut self, contents: impl Into>) -> &mut Self { + let contents = contents.into(); + self.envfile_contents = Some(contents); self } @@ -232,13 +223,27 @@ impl TestEnv { &self.env_vars } - /// Get a reference to the string that will be placed in the envfile. + /// Get a reference to the bytes that will be placed in the envfile. /// /// If `None` is returned, an envfile will not be created - pub fn envfile_contents(&self) -> Option<&str> { + pub fn envfile_contents_as_bytes(&self) -> Option<&[u8]> { self.envfile_contents.as_deref() } + /// Get a reference to the string that will be placed in the envfile. + /// + /// If `None` is returned, an envfile will not be created + /// + /// ## Panics + /// + /// This will panic if the envfile contents are not valid UTF-8 + pub fn envfile_contents_as_str(&self) -> Option<&str> { + self.envfile_contents_as_bytes().map(|bytes| { + let out = std::str::from_utf8(bytes).expect("valid UTF-8"); + Some(out) + })? + } + /// Get a reference to the path of the envfile. pub fn envfile_path(&self) -> &Path { &self.envfile_path @@ -253,7 +258,7 @@ impl Default for TestEnv { key: TEST_EXISTING_KEY.into(), value: TEST_EXISTING_VALUE.into(), }]; - let envfile_contents = Some(create_default_envfile()); + let envfile_contents = Some(create_default_envfile().into()); let envfile_path = work_dir.join(".env"); Self { temp_dir, @@ -307,7 +312,7 @@ fn reset_env(original_env: &EnvMap) { /// Writes the envfile, sets the working directory, and sets environment vars. fn create_env(test_env: &TestEnv) { // only create the envfile if its contents has been set - if let Some(contents) = test_env.envfile_contents() { + if let Some(contents) = test_env.envfile_contents_as_bytes() { create_envfile(&test_env.envfile_path, contents); } @@ -319,14 +324,14 @@ fn create_env(test_env: &TestEnv) { } /// Create an envfile for use in tests. -fn create_envfile(path: &Path, contents: &str) { +fn create_envfile(path: &Path, contents: &[u8]) { if path.exists() { panic!("envfile `{}` already exists", path.display()) } // inner function to group together io::Results - fn create_env_file_inner(path: &Path, contents: &str) -> io::Result<()> { + fn create_env_file_inner(path: &Path, contents: &[u8]) -> io::Result<()> { let mut file = fs::File::create(path)?; - file.write_all(contents.as_bytes())?; + file.write_all(contents)?; file.sync_all() } // call inner function From 48d46099a46c5df47d997aa0aaf205f4d68af515 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 12:44:02 +0100 Subject: [PATCH 06/44] Add more test harness assertions --- dotenv/tests/integration/util/mod.rs | 29 +++++++++++ dotenv/tests/integration/util/tests.rs | 66 ++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index f77d5605..394d8cbb 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -34,6 +34,35 @@ pub fn create_invalid_envfile() -> String { ) } +pub fn create_custom_envfile(env_vars: &[(&str, &str)]) -> String { + let mut envfile = String::new(); + for (key, value) in env_vars { + envfile.push_str(key); + envfile.push('='); + envfile.push_str(value); + envfile.push('\n'); + } + envfile +} + +/// Assert that a slice of environment variables are set and have the expected +/// values. +/// +/// ## Arguments +/// +/// * `vars` - A slice of key-expected value tuples +/// +/// ## Examples +/// +/// ```rust +/// assert_env_vars(&[("TESTKEY", "test_val"), ("TEST_EXISTING_KEY", "from_env")]); +/// ``` +pub fn assert_env_vars(vars: &[(&str, &str)]) { + for (key, expected) in vars { + assert_env_var(key, expected); + } +} + /// Assert that an environment variable is set and has the expected value. pub fn assert_env_var(key: &str, expected: &str) { match env::var(key) { diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index 02ba6792..0742a510 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -2,6 +2,13 @@ use std::path::PathBuf; use super::*; +const CUSTOM_VARS: &[(&str, &str)] = &[ + ("CUSTOM_KEY_1", "CUSTOM_VALUE_1"), + ("CUSTOM_KEY_2", "CUSTOM_VALUE_2"), +]; + +const DOTENV_EXPECT: &str = "TestEnv should have .env file"; + mod default_env { use super::*; @@ -22,7 +29,7 @@ mod default_env { #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { - dotenv_wrapper().expect("Default TestEnv should have .env file"); + dotenv_wrapper().expect(DOTENV_EXPECT); // dotenv() does not override existing var assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); assert_env_var(TEST_KEY, TEST_VALUE); @@ -56,27 +63,64 @@ mod testenv_init_with_envfile { #[test] fn default_envfile_vars_state() { - let testenv = TestEnv::init_with_envfile(create_default_envfile()); + let testenv = init_default_testenv(); assert_default_keys_not_set_in_testenv(testenv); } #[test] fn default_envfile_exists() { - let testenv = TestEnv::init_with_envfile(create_default_envfile()); + let testenv = init_default_testenv(); assert_envfile_exists_in_testenv(testenv); } #[test] fn default_envfile_loaded_vars_state() { - let init_testenv = TestEnv::init_with_envfile(create_default_envfile()); - test_in_env(init_testenv, || { - dotenv_wrapper().expect("Default TestEnv should have .env file"); + let testenv = init_default_testenv(); + test_in_env(testenv, || { + dotenv_wrapper().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); assert_env_var(TEST_KEY, TEST_VALUE); }); } + + #[test] + fn custom_envfile_vars_state() { + let testenv = init_custom_testenv(); + test_in_env(testenv, || { + assert_default_keys_not_set(); + for (key, _) in CUSTOM_VARS { + assert_env_var_unset(key); + } + }); + } + + #[test] + fn custom_envfile_exists() { + let testenv = init_custom_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn custom_envfile_loaded_vars_state() { + let testenv = init_custom_testenv(); + test_in_env(testenv, || { + dotenv_wrapper().expect(DOTENV_EXPECT); + assert_default_keys_not_set(); + assert_env_vars(CUSTOM_VARS); + }); + } + + fn init_default_testenv() -> TestEnv { + let envfile = create_default_envfile(); + TestEnv::init_with_envfile(envfile) + } + + fn init_custom_testenv() -> TestEnv { + let envfile = create_custom_envfile(CUSTOM_VARS); + TestEnv::init_with_envfile(envfile) + } } fn assert_envfile_exists_in_testenv(testenv: TestEnv) { @@ -89,10 +133,12 @@ fn assert_envfile_exists_in_testenv(testenv: TestEnv) { } fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { - test_in_env(testenv, || { - assert_env_var_unset(TEST_EXISTING_KEY); - assert_env_var_unset(TEST_KEY); - }); + test_in_env(testenv, assert_default_keys_not_set); +} + +fn assert_default_keys_not_set() { + assert_env_var_unset(TEST_EXISTING_KEY); + assert_env_var_unset(TEST_KEY); } fn dotenv_wrapper() -> dotenvy::Result { From 461b9eb947f896ad9555acd56c43e62111a8e310 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 13:33:35 +0100 Subject: [PATCH 07/44] Add API wrapper to test harness --- dotenv/tests/integration/util/mod.rs | 2 + dotenv/tests/integration/util/tests.rs | 16 ++--- dotenv/tests/integration/util/wrapper.rs | 80 ++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 dotenv/tests/integration/util/wrapper.rs diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index 394d8cbb..89da3151 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -2,8 +2,10 @@ use std::env::{self, VarError}; mod testenv; mod tests; +mod wrapper; pub use testenv::{test_in_default_env, test_in_env, KeyVal, TestEnv}; +pub use wrapper::*; /// Default key used in envfile pub const TEST_KEY: &str = "TESTKEY"; diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index 0742a510..f6c4cb72 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use super::*; const CUSTOM_VARS: &[(&str, &str)] = &[ @@ -29,7 +27,7 @@ mod default_env { #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { - dotenv_wrapper().expect(DOTENV_EXPECT); + dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); assert_env_var(TEST_KEY, TEST_VALUE); @@ -53,7 +51,7 @@ mod testenv_init { test_in_env(init_testenv, || { assert!(!envfile_path.exists()); - assert!(dotenv_wrapper().is_err()); + assert!(dotenv_wrap().is_err()); }); } } @@ -77,7 +75,7 @@ mod testenv_init_with_envfile { fn default_envfile_loaded_vars_state() { let testenv = init_default_testenv(); test_in_env(testenv, || { - dotenv_wrapper().expect(DOTENV_EXPECT); + dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); @@ -106,7 +104,7 @@ mod testenv_init_with_envfile { fn custom_envfile_loaded_vars_state() { let testenv = init_custom_testenv(); test_in_env(testenv, || { - dotenv_wrapper().expect(DOTENV_EXPECT); + dotenv_wrap().expect(DOTENV_EXPECT); assert_default_keys_not_set(); assert_env_vars(CUSTOM_VARS); }); @@ -128,7 +126,7 @@ fn assert_envfile_exists_in_testenv(testenv: TestEnv) { test_in_env(testenv, || { assert!(envfile_path.exists()); - assert!(dotenv_wrapper().is_ok()); + assert!(dotenv_wrap().is_ok()); }); } @@ -140,7 +138,3 @@ fn assert_default_keys_not_set() { assert_env_var_unset(TEST_EXISTING_KEY); assert_env_var_unset(TEST_KEY); } - -fn dotenv_wrapper() -> dotenvy::Result { - dotenvy::dotenv() -} diff --git a/dotenv/tests/integration/util/wrapper.rs b/dotenv/tests/integration/util/wrapper.rs new file mode 100644 index 00000000..116791f7 --- /dev/null +++ b/dotenv/tests/integration/util/wrapper.rs @@ -0,0 +1,80 @@ +//! Wrappers for the `dotenvy` API. +//! +//! If the `dotenvy` API changes, only this module needs to be updated. + +use dotenvy::{self, Iter, Result}; +use std::env::Vars; +use std::ffi::OsStr; +use std::fs::File; +use std::io; +use std::path::{Path, PathBuf}; + +#[inline(always)] +pub fn var_wrap>(key: K) -> Result { + dotenvy::var(key) +} + +#[inline(always)] +pub fn vars_wrap() -> Vars { + dotenvy::vars() +} + +#[inline(always)] +pub fn from_path_wrap>(path: P) -> Result<()> { + dotenvy::from_path(path) +} + +#[inline(always)] +pub fn from_path_override_wrap>(path: P) -> Result<()> { + dotenvy::from_path_override(path) +} + +#[inline(always)] +pub fn from_path_iter_wrap>(path: P) -> Result> { + dotenvy::from_path_iter(path) +} + +#[inline(always)] +pub fn from_filename_wrap>(filename: P) -> Result { + dotenvy::from_filename(filename) +} + +#[inline(always)] +pub fn from_filename_override_wrap>(filename: P) -> Result { + dotenvy::from_filename_override(filename) +} + +#[inline(always)] +pub fn from_filename_iter_wrap>(filename: P) -> Result> { + dotenvy::from_filename_iter(filename) +} + +#[inline(always)] +pub fn from_read_wrap(reader: R) -> Result<()> { + dotenvy::from_read(reader) +} + +#[inline(always)] +pub fn from_read_override_wrap(reader: R) -> Result<()> { + dotenvy::from_read_override(reader) +} + +#[inline(always)] +pub fn from_read_iter_wrap(reader: R) -> Iter { + dotenvy::from_read_iter(reader) +} + +#[inline(always)] +pub fn dotenv_wrap() -> Result { + dotenvy::dotenv() +} + +#[inline(always)] +pub fn dotenv_override_wrap() -> Result { + dotenvy::dotenv_override() +} + +#[inline(always)] +pub fn dotenv_iter_wrap() -> Result> { + dotenvy::dotenv_iter() +} From 240d0f54f06a3d28bfaf620a602ea17286fabe44 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 13:47:56 +0100 Subject: [PATCH 08/44] Test empty envfile in harness --- dotenv/tests/integration/util/tests.rs | 35 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index f6c4cb72..303b0278 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -61,19 +61,19 @@ mod testenv_init_with_envfile { #[test] fn default_envfile_vars_state() { - let testenv = init_default_testenv(); + let testenv = init_default_envfile_testenv(); assert_default_keys_not_set_in_testenv(testenv); } #[test] fn default_envfile_exists() { - let testenv = init_default_testenv(); + let testenv = init_default_envfile_testenv(); assert_envfile_exists_in_testenv(testenv); } #[test] fn default_envfile_loaded_vars_state() { - let testenv = init_default_testenv(); + let testenv = init_default_envfile_testenv(); test_in_env(testenv, || { dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var @@ -85,7 +85,7 @@ mod testenv_init_with_envfile { #[test] fn custom_envfile_vars_state() { - let testenv = init_custom_testenv(); + let testenv = init_custom_envfile_testenv(); test_in_env(testenv, || { assert_default_keys_not_set(); for (key, _) in CUSTOM_VARS { @@ -96,13 +96,13 @@ mod testenv_init_with_envfile { #[test] fn custom_envfile_exists() { - let testenv = init_custom_testenv(); + let testenv = init_custom_envfile_testenv(); assert_envfile_exists_in_testenv(testenv); } #[test] fn custom_envfile_loaded_vars_state() { - let testenv = init_custom_testenv(); + let testenv = init_custom_envfile_testenv(); test_in_env(testenv, || { dotenv_wrap().expect(DOTENV_EXPECT); assert_default_keys_not_set(); @@ -110,15 +110,34 @@ mod testenv_init_with_envfile { }); } - fn init_default_testenv() -> TestEnv { + #[test] + fn empty_envfile_exists() { + let testenv = init_empty_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn empty_envfile_loaded_vars_state() { + let testenv = init_empty_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + assert_default_keys_not_set(); + }); + } + + fn init_default_envfile_testenv() -> TestEnv { let envfile = create_default_envfile(); TestEnv::init_with_envfile(envfile) } - fn init_custom_testenv() -> TestEnv { + fn init_custom_envfile_testenv() -> TestEnv { let envfile = create_custom_envfile(CUSTOM_VARS); TestEnv::init_with_envfile(envfile) } + + fn init_empty_envfile_testenv() -> TestEnv { + TestEnv::init_with_envfile([]) + } } fn assert_envfile_exists_in_testenv(testenv: TestEnv) { From 97e69b5b9c8c23e85d00d856e6d29e97fe805d61 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 14:31:05 +0100 Subject: [PATCH 09/44] Add EnvFileBuilder to test harness --- dotenv/tests/integration/util/envfile.rs | 96 ++++++++++++++++++++ dotenv/tests/integration/util/mod.rs | 13 ++- dotenv/tests/integration/util/tests.rs | 109 +++++++++++++++++++++++ 3 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 dotenv/tests/integration/util/envfile.rs diff --git a/dotenv/tests/integration/util/envfile.rs b/dotenv/tests/integration/util/envfile.rs new file mode 100644 index 00000000..4516823c --- /dev/null +++ b/dotenv/tests/integration/util/envfile.rs @@ -0,0 +1,96 @@ +/// Create envfile contents for testing +/// +/// Represented as bytes to allow for advanced manipulation and BOM testing. +#[derive(Debug, Default)] +pub struct EnvFileBuilder { + contents: Vec, +} + +impl EnvFileBuilder { + pub fn new() -> Self { + Self { + contents: Vec::new(), + } + } + + /// Build a byte vector from the contents of the builder. + pub fn build(&self) -> Vec { + self.contents.clone() + } + + /// Build a string from the contents of the builder. + /// + /// ## Panics + /// + /// If the contents of the builder are not valid UTF-8. + pub fn build_string(&self) -> String { + String::from_utf8(self.contents.clone()).expect("valid UTF-8") + } + + /// Transform the builder into a byte vector. + pub fn into_owned_bytes(self) -> Vec { + self.contents + } + + /// Transform the builder into a string. + /// + /// ## Panics + /// + /// If the contents of the builder are not valid UTF-8. + pub fn into_owned_string(self) -> String { + String::from_utf8(self.contents).expect("valid UTF-8") + } + + /// Get a reference to the contents of the builder. + pub fn as_bytes(&self) -> &[u8] { + &self.contents + } + + /// Add + pub fn add_vars(&mut self, env_vars: &[(&str, &str)]) -> &mut Self { + let mut many = String::new(); + for (key, value) in env_vars { + many.push_str(key); + many.push('='); + many.push_str(value); + many.push('\n'); + } + self.add_str(&many); + self + } + + /// Add a key-value pair and newline + pub fn add_key_value(&mut self, key: &str, value: &str) -> &mut Self { + self.add_strln(&format!("{}={}", key, value)) + } + + /// Add a string without a newline + pub fn add_str(&mut self, s: &str) -> &mut Self { + self.add_bytes(s.as_bytes()) + } + + /// Add a string with a newline + pub fn add_strln(&mut self, line: &str) -> &mut Self { + self.add_str(line).add_byte(b'\n') + } + + /// Add a byte slice + pub fn add_bytes(&mut self, bytes: &[u8]) -> &mut Self { + self.contents.extend_from_slice(bytes); + self + } + + /// Add a single byte + pub fn add_byte(&mut self, byte: u8) -> &mut Self { + self.contents.push(byte); + self + } + + /// Insert the UTF-8 Byte Order Mark at the beginning of the file + pub fn insert_utf8_bom(&mut self) -> &mut Self { + // https://www.compart.com/en/unicode/U+FEFF + let bom = b"\xEF\xBB\xBF"; + self.contents.splice(0..0, bom.iter().cloned()); + self + } +} diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index 89da3151..98423b43 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -1,9 +1,11 @@ use std::env::{self, VarError}; +mod envfile; mod testenv; mod tests; mod wrapper; +pub use envfile::EnvFileBuilder; pub use testenv::{test_in_default_env, test_in_env, KeyVal, TestEnv}; pub use wrapper::*; @@ -37,14 +39,9 @@ pub fn create_invalid_envfile() -> String { } pub fn create_custom_envfile(env_vars: &[(&str, &str)]) -> String { - let mut envfile = String::new(); - for (key, value) in env_vars { - envfile.push_str(key); - envfile.push('='); - envfile.push_str(value); - envfile.push('\n'); - } - envfile + let mut efb = EnvFileBuilder::new(); + efb.add_vars(env_vars); + efb.into_owned_string() } /// Assert that a slice of environment variables are set and have the expected diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index 303b0278..a6c0b40c 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -140,6 +140,115 @@ mod testenv_init_with_envfile { } } +mod envfile_builder { + use super::*; + + #[test] + fn new_builds_empty() { + let efb = EnvFileBuilder::new(); + assert_contents_empty(efb); + } + + #[test] + fn default_builds_empty() { + let efb = EnvFileBuilder::default(); + assert_contents_empty(efb); + } + + #[test] + fn add_key_empty_value() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, ""); + let expected = format!("{}=\n", TEST_KEY); + assert_contents_str(efb, &expected); + } + + #[test] + fn add_key_value() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + let expected = format!("{}={}\n", TEST_KEY, TEST_VALUE); + assert_contents_str(efb, &expected); + } + + #[test] + fn add_multiple_key_values() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + efb.add_key_value(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); + let expected = expected_envfile(&[ + (TEST_KEY, TEST_VALUE), + (TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE), + ]); + assert_contents_str(efb, &expected); + } + + #[test] + fn add_vars() { + let mut efb = EnvFileBuilder::new(); + efb.add_vars(CUSTOM_VARS); + let expected = expected_envfile(CUSTOM_VARS); + assert_contents_str(efb, &expected); + } + + #[test] + fn add_str() { + let mut efb = EnvFileBuilder::new(); + efb.add_str("test"); + assert_contents_str(efb, "test"); + } + + #[test] + fn add_bytes() { + let mut efb = EnvFileBuilder::new(); + efb.add_bytes(b"test"); + assert_contents_str(efb, "test"); + } + + #[test] + fn add_byte() { + let mut efb = EnvFileBuilder::new(); + efb.add_byte(b't'); + assert_contents_str(efb, "t"); + } + + #[test] + fn insert_utf8_bom() { + let mut efb = EnvFileBuilder::new(); + efb.add_str("test"); + efb.insert_utf8_bom(); + assert_contents_str(efb, "\u{FEFF}test"); + } + + #[test] + fn add_strln() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln("test"); + assert_contents_str(efb, "test\n"); + } + + fn assert_contents_empty(efb: EnvFileBuilder) { + let contents = efb.into_owned_bytes(); + assert!(contents.is_empty()); + } + + fn assert_contents_str(efb: EnvFileBuilder, expected: &str) { + let contents = efb.into_owned_string(); + assert_eq!(expected, contents,); + } + + fn expected_envfile(env_vars: &[(&str, &str)]) -> String { + let mut envfile = String::new(); + for (key, value) in env_vars { + envfile.push_str(key); + envfile.push('='); + envfile.push_str(value); + envfile.push('\n'); + } + envfile + } +} + fn assert_envfile_exists_in_testenv(testenv: TestEnv) { let envfile_path = testenv.envfile_path().to_owned(); From 88386acd35d38fe48a3fb7af357b8c0ce7eb7e75 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Fri, 12 Jul 2024 14:42:34 +0100 Subject: [PATCH 10/44] Test harness BOM handling --- dotenv/tests/integration/util/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs index a6c0b40c..3262f455 100644 --- a/dotenv/tests/integration/util/tests.rs +++ b/dotenv/tests/integration/util/tests.rs @@ -125,6 +125,21 @@ mod testenv_init_with_envfile { }); } + #[test] + fn custom_bom_envfile_exists() { + let testenv = init_custom_bom_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn custom_bom_envfile_loaded_vars_state() { + let testenv = init_custom_bom_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + assert_env_var(TEST_KEY, TEST_VALUE); + }); + } + fn init_default_envfile_testenv() -> TestEnv { let envfile = create_default_envfile(); TestEnv::init_with_envfile(envfile) @@ -138,6 +153,14 @@ mod testenv_init_with_envfile { fn init_empty_envfile_testenv() -> TestEnv { TestEnv::init_with_envfile([]) } + + fn init_custom_bom_envfile_testenv() -> TestEnv { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + efb.insert_utf8_bom(); + let envfile = efb.into_owned_string(); + TestEnv::init_with_envfile(envfile) + } } mod envfile_builder { From d872f141c7dca12ad7358e694df5cb7fa89dcf8d Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Sun, 14 Jul 2024 11:05:17 +0100 Subject: [PATCH 11/44] Move test harness tests to subdir --- dotenv/tests/integration/util/tests.rs | 291 ------------------ .../integration/util/tests/default_env.rs | 25 ++ .../integration/util/tests/envfile_builder.rs | 106 +++++++ dotenv/tests/integration/util/tests/mod.rs | 30 ++ .../tests/integration/util/tests/testenv.rs | 129 ++++++++ 5 files changed, 290 insertions(+), 291 deletions(-) delete mode 100644 dotenv/tests/integration/util/tests.rs create mode 100644 dotenv/tests/integration/util/tests/default_env.rs create mode 100644 dotenv/tests/integration/util/tests/envfile_builder.rs create mode 100644 dotenv/tests/integration/util/tests/mod.rs create mode 100644 dotenv/tests/integration/util/tests/testenv.rs diff --git a/dotenv/tests/integration/util/tests.rs b/dotenv/tests/integration/util/tests.rs deleted file mode 100644 index 3262f455..00000000 --- a/dotenv/tests/integration/util/tests.rs +++ /dev/null @@ -1,291 +0,0 @@ -use super::*; - -const CUSTOM_VARS: &[(&str, &str)] = &[ - ("CUSTOM_KEY_1", "CUSTOM_VALUE_1"), - ("CUSTOM_KEY_2", "CUSTOM_VALUE_2"), -]; - -const DOTENV_EXPECT: &str = "TestEnv should have .env file"; - -mod default_env { - use super::*; - - #[test] - fn vars_state() { - test_in_default_env(|| { - assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); - assert_env_var_unset(TEST_KEY); - }); - } - - #[test] - fn envfile_exists() { - let testenv = TestEnv::default(); - assert_envfile_exists_in_testenv(testenv); - } - - #[test] - fn envfile_loaded_vars_state() { - test_in_default_env(|| { - dotenv_wrap().expect(DOTENV_EXPECT); - // dotenv() does not override existing var - assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); - assert_env_var(TEST_KEY, TEST_VALUE); - }); - } -} - -mod testenv_init { - use super::*; - - #[test] - fn vars_state() { - let init_testenv = TestEnv::init(); - assert_default_keys_not_set_in_testenv(init_testenv); - } - - #[test] - fn no_envfile() { - let init_testenv = TestEnv::init(); - let envfile_path = init_testenv.envfile_path().to_owned(); - - test_in_env(init_testenv, || { - assert!(!envfile_path.exists()); - assert!(dotenv_wrap().is_err()); - }); - } -} - -mod testenv_init_with_envfile { - use super::*; - - #[test] - fn default_envfile_vars_state() { - let testenv = init_default_envfile_testenv(); - assert_default_keys_not_set_in_testenv(testenv); - } - - #[test] - fn default_envfile_exists() { - let testenv = init_default_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); - } - - #[test] - fn default_envfile_loaded_vars_state() { - let testenv = init_default_envfile_testenv(); - test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); - // dotenv() does not override existing var - // but existing key is not set in this testenv - assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); - assert_env_var(TEST_KEY, TEST_VALUE); - }); - } - - #[test] - fn custom_envfile_vars_state() { - let testenv = init_custom_envfile_testenv(); - test_in_env(testenv, || { - assert_default_keys_not_set(); - for (key, _) in CUSTOM_VARS { - assert_env_var_unset(key); - } - }); - } - - #[test] - fn custom_envfile_exists() { - let testenv = init_custom_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); - } - - #[test] - fn custom_envfile_loaded_vars_state() { - let testenv = init_custom_envfile_testenv(); - test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); - assert_default_keys_not_set(); - assert_env_vars(CUSTOM_VARS); - }); - } - - #[test] - fn empty_envfile_exists() { - let testenv = init_empty_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); - } - - #[test] - fn empty_envfile_loaded_vars_state() { - let testenv = init_empty_envfile_testenv(); - test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); - assert_default_keys_not_set(); - }); - } - - #[test] - fn custom_bom_envfile_exists() { - let testenv = init_custom_bom_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); - } - - #[test] - fn custom_bom_envfile_loaded_vars_state() { - let testenv = init_custom_bom_envfile_testenv(); - test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); - assert_env_var(TEST_KEY, TEST_VALUE); - }); - } - - fn init_default_envfile_testenv() -> TestEnv { - let envfile = create_default_envfile(); - TestEnv::init_with_envfile(envfile) - } - - fn init_custom_envfile_testenv() -> TestEnv { - let envfile = create_custom_envfile(CUSTOM_VARS); - TestEnv::init_with_envfile(envfile) - } - - fn init_empty_envfile_testenv() -> TestEnv { - TestEnv::init_with_envfile([]) - } - - fn init_custom_bom_envfile_testenv() -> TestEnv { - let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); - efb.insert_utf8_bom(); - let envfile = efb.into_owned_string(); - TestEnv::init_with_envfile(envfile) - } -} - -mod envfile_builder { - use super::*; - - #[test] - fn new_builds_empty() { - let efb = EnvFileBuilder::new(); - assert_contents_empty(efb); - } - - #[test] - fn default_builds_empty() { - let efb = EnvFileBuilder::default(); - assert_contents_empty(efb); - } - - #[test] - fn add_key_empty_value() { - let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, ""); - let expected = format!("{}=\n", TEST_KEY); - assert_contents_str(efb, &expected); - } - - #[test] - fn add_key_value() { - let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); - let expected = format!("{}={}\n", TEST_KEY, TEST_VALUE); - assert_contents_str(efb, &expected); - } - - #[test] - fn add_multiple_key_values() { - let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); - efb.add_key_value(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); - let expected = expected_envfile(&[ - (TEST_KEY, TEST_VALUE), - (TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE), - ]); - assert_contents_str(efb, &expected); - } - - #[test] - fn add_vars() { - let mut efb = EnvFileBuilder::new(); - efb.add_vars(CUSTOM_VARS); - let expected = expected_envfile(CUSTOM_VARS); - assert_contents_str(efb, &expected); - } - - #[test] - fn add_str() { - let mut efb = EnvFileBuilder::new(); - efb.add_str("test"); - assert_contents_str(efb, "test"); - } - - #[test] - fn add_bytes() { - let mut efb = EnvFileBuilder::new(); - efb.add_bytes(b"test"); - assert_contents_str(efb, "test"); - } - - #[test] - fn add_byte() { - let mut efb = EnvFileBuilder::new(); - efb.add_byte(b't'); - assert_contents_str(efb, "t"); - } - - #[test] - fn insert_utf8_bom() { - let mut efb = EnvFileBuilder::new(); - efb.add_str("test"); - efb.insert_utf8_bom(); - assert_contents_str(efb, "\u{FEFF}test"); - } - - #[test] - fn add_strln() { - let mut efb = EnvFileBuilder::new(); - efb.add_strln("test"); - assert_contents_str(efb, "test\n"); - } - - fn assert_contents_empty(efb: EnvFileBuilder) { - let contents = efb.into_owned_bytes(); - assert!(contents.is_empty()); - } - - fn assert_contents_str(efb: EnvFileBuilder, expected: &str) { - let contents = efb.into_owned_string(); - assert_eq!(expected, contents,); - } - - fn expected_envfile(env_vars: &[(&str, &str)]) -> String { - let mut envfile = String::new(); - for (key, value) in env_vars { - envfile.push_str(key); - envfile.push('='); - envfile.push_str(value); - envfile.push('\n'); - } - envfile - } -} - -fn assert_envfile_exists_in_testenv(testenv: TestEnv) { - let envfile_path = testenv.envfile_path().to_owned(); - - test_in_env(testenv, || { - assert!(envfile_path.exists()); - assert!(dotenv_wrap().is_ok()); - }); -} - -fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { - test_in_env(testenv, assert_default_keys_not_set); -} - -fn assert_default_keys_not_set() { - assert_env_var_unset(TEST_EXISTING_KEY); - assert_env_var_unset(TEST_KEY); -} diff --git a/dotenv/tests/integration/util/tests/default_env.rs b/dotenv/tests/integration/util/tests/default_env.rs new file mode 100644 index 00000000..de6e8e96 --- /dev/null +++ b/dotenv/tests/integration/util/tests/default_env.rs @@ -0,0 +1,25 @@ +use super::*; + +#[test] +fn vars_state() { + test_in_default_env(|| { + assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); + assert_env_var_unset(TEST_KEY); + }); +} + +#[test] +fn envfile_exists() { + let testenv = TestEnv::default(); + assert_envfile_exists_in_testenv(testenv); +} + +#[test] +fn envfile_loaded_vars_state() { + test_in_default_env(|| { + dotenv_wrap().expect(DOTENV_EXPECT); + // dotenv() does not override existing var + assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); + assert_env_var(TEST_KEY, TEST_VALUE); + }); +} diff --git a/dotenv/tests/integration/util/tests/envfile_builder.rs b/dotenv/tests/integration/util/tests/envfile_builder.rs new file mode 100644 index 00000000..4bd431a2 --- /dev/null +++ b/dotenv/tests/integration/util/tests/envfile_builder.rs @@ -0,0 +1,106 @@ +use super::*; + +#[test] +fn new_builds_empty() { + let efb = EnvFileBuilder::new(); + assert_contents_empty(efb); +} + +#[test] +fn default_builds_empty() { + let efb = EnvFileBuilder::default(); + assert_contents_empty(efb); +} + +#[test] +fn add_key_empty_value() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, ""); + let expected = format!("{}=\n", TEST_KEY); + assert_contents_str(efb, &expected); +} + +#[test] +fn add_key_value() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + let expected = format!("{}={}\n", TEST_KEY, TEST_VALUE); + assert_contents_str(efb, &expected); +} + +#[test] +fn add_multiple_key_values() { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + efb.add_key_value(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); + let expected = expected_envfile(&[ + (TEST_KEY, TEST_VALUE), + (TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE), + ]); + assert_contents_str(efb, &expected); +} + +#[test] +fn add_vars() { + let mut efb = EnvFileBuilder::new(); + efb.add_vars(CUSTOM_VARS); + let expected = expected_envfile(CUSTOM_VARS); + assert_contents_str(efb, &expected); +} + +#[test] +fn add_str() { + let mut efb = EnvFileBuilder::new(); + efb.add_str("test"); + assert_contents_str(efb, "test"); +} + +#[test] +fn add_bytes() { + let mut efb = EnvFileBuilder::new(); + efb.add_bytes(b"test"); + assert_contents_str(efb, "test"); +} + +#[test] +fn add_byte() { + let mut efb = EnvFileBuilder::new(); + efb.add_byte(b't'); + assert_contents_str(efb, "t"); +} + +#[test] +fn insert_utf8_bom() { + let mut efb = EnvFileBuilder::new(); + efb.add_str("test"); + efb.insert_utf8_bom(); + assert_contents_str(efb, "\u{FEFF}test"); +} + +#[test] +fn add_strln() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln("test"); + assert_contents_str(efb, "test\n"); +} + +fn assert_contents_empty(efb: EnvFileBuilder) { + let contents = efb.into_owned_bytes(); + assert!(contents.is_empty()); +} + +fn assert_contents_str(efb: EnvFileBuilder, expected: &str) { + let contents = efb.into_owned_string(); + assert_eq!(expected, contents,); +} + +fn expected_envfile(env_vars: &[(&str, &str)]) -> String { + let mut envfile = String::new(); + for (key, value) in env_vars { + envfile.push_str(key); + envfile.push('='); + envfile.push_str(value); + envfile.push('\n'); + } + envfile +} diff --git a/dotenv/tests/integration/util/tests/mod.rs b/dotenv/tests/integration/util/tests/mod.rs new file mode 100644 index 00000000..8f7c34ce --- /dev/null +++ b/dotenv/tests/integration/util/tests/mod.rs @@ -0,0 +1,30 @@ +use super::*; + +mod default_env; +mod envfile_builder; +mod testenv; + +const CUSTOM_VARS: &[(&str, &str)] = &[ + ("CUSTOM_KEY_1", "CUSTOM_VALUE_1"), + ("CUSTOM_KEY_2", "CUSTOM_VALUE_2"), +]; + +const DOTENV_EXPECT: &str = "TestEnv should have .env file"; + +fn assert_envfile_exists_in_testenv(testenv: TestEnv) { + let envfile_path = testenv.envfile_path().to_owned(); + + test_in_env(testenv, || { + assert!(envfile_path.exists()); + assert!(dotenv_wrap().is_ok()); + }); +} + +fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { + test_in_env(testenv, assert_default_keys_not_set); +} + +fn assert_default_keys_not_set() { + assert_env_var_unset(TEST_EXISTING_KEY); + assert_env_var_unset(TEST_KEY); +} diff --git a/dotenv/tests/integration/util/tests/testenv.rs b/dotenv/tests/integration/util/tests/testenv.rs new file mode 100644 index 00000000..fb24edfe --- /dev/null +++ b/dotenv/tests/integration/util/tests/testenv.rs @@ -0,0 +1,129 @@ +use super::*; + +mod init { + use super::*; + + #[test] + fn vars_state() { + let init_testenv = TestEnv::init(); + assert_default_keys_not_set_in_testenv(init_testenv); + } + + #[test] + fn no_envfile() { + let init_testenv = TestEnv::init(); + let envfile_path = init_testenv.envfile_path().to_owned(); + + test_in_env(init_testenv, || { + assert!(!envfile_path.exists()); + assert!(dotenv_wrap().is_err()); + }); + } +} + +mod init_with_envfile { + use super::*; + + #[test] + fn default_envfile_vars_state() { + let testenv = init_default_envfile_testenv(); + assert_default_keys_not_set_in_testenv(testenv); + } + + #[test] + fn default_envfile_exists() { + let testenv = init_default_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn default_envfile_loaded_vars_state() { + let testenv = init_default_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + // dotenv() does not override existing var + // but existing key is not set in this testenv + assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); + assert_env_var(TEST_KEY, TEST_VALUE); + }); + } + + #[test] + fn custom_envfile_vars_state() { + let testenv = init_custom_envfile_testenv(); + test_in_env(testenv, || { + assert_default_keys_not_set(); + for (key, _) in CUSTOM_VARS { + assert_env_var_unset(key); + } + }); + } + + #[test] + fn custom_envfile_exists() { + let testenv = init_custom_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn custom_envfile_loaded_vars_state() { + let testenv = init_custom_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + assert_default_keys_not_set(); + assert_env_vars(CUSTOM_VARS); + }); + } + + #[test] + fn empty_envfile_exists() { + let testenv = init_empty_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn empty_envfile_loaded_vars_state() { + let testenv = init_empty_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + assert_default_keys_not_set(); + }); + } + + #[test] + fn custom_bom_envfile_exists() { + let testenv = init_custom_bom_envfile_testenv(); + assert_envfile_exists_in_testenv(testenv); + } + + #[test] + fn custom_bom_envfile_loaded_vars_state() { + let testenv = init_custom_bom_envfile_testenv(); + test_in_env(testenv, || { + dotenv_wrap().expect(DOTENV_EXPECT); + assert_env_var(TEST_KEY, TEST_VALUE); + }); + } + + fn init_default_envfile_testenv() -> TestEnv { + let envfile = create_default_envfile(); + TestEnv::init_with_envfile(envfile) + } + + fn init_custom_envfile_testenv() -> TestEnv { + let envfile = create_custom_envfile(CUSTOM_VARS); + TestEnv::init_with_envfile(envfile) + } + + fn init_empty_envfile_testenv() -> TestEnv { + TestEnv::init_with_envfile([]) + } + + fn init_custom_bom_envfile_testenv() -> TestEnv { + let mut efb = EnvFileBuilder::new(); + efb.add_key_value(TEST_KEY, TEST_VALUE); + efb.insert_utf8_bom(); + let envfile = efb.into_owned_string(); + TestEnv::init_with_envfile(envfile) + } +} From e870d600328728e7051adf311675316a69d36c3b Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Sun, 14 Jul 2024 11:31:25 +0100 Subject: [PATCH 12/44] Clean harness utility layout --- dotenv/tests/integration/dotenv.rs | 4 +- dotenv/tests/integration/util/assertions.rs | 62 +++++++++++++ dotenv/tests/integration/util/envfile.rs | 37 +++++++- dotenv/tests/integration/util/mod.rs | 92 ++----------------- dotenv/tests/integration/util/testenv.rs | 16 ++-- .../integration/util/tests/default_env.rs | 8 +- .../integration/util/tests/envfile_builder.rs | 16 ++-- dotenv/tests/integration/util/tests/mod.rs | 7 +- .../tests/integration/util/tests/testenv.rs | 14 +-- 9 files changed, 137 insertions(+), 119 deletions(-) create mode 100644 dotenv/tests/integration/util/assertions.rs diff --git a/dotenv/tests/integration/dotenv.rs b/dotenv/tests/integration/dotenv.rs index 966fbf23..a47fc423 100644 --- a/dotenv/tests/integration/dotenv.rs +++ b/dotenv/tests/integration/dotenv.rs @@ -4,7 +4,7 @@ use crate::util::*; fn dotenv_ok_default_env() { test_in_default_env(|| { dotenvy::dotenv().ok(); - assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); - assert_env_var(TEST_KEY, TEST_VALUE); + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } diff --git a/dotenv/tests/integration/util/assertions.rs b/dotenv/tests/integration/util/assertions.rs new file mode 100644 index 00000000..fbaff16b --- /dev/null +++ b/dotenv/tests/integration/util/assertions.rs @@ -0,0 +1,62 @@ +use super::*; +use std::env::{self, VarError}; + +/// Assert that a slice of environment variables are set and have the expected +/// values. +/// +/// ## Arguments +/// +/// * `vars` - A slice of key-expected value tuples +/// +/// ## Examples +/// +/// ```rust +/// assert_env_vars(&[ +/// ("DEFAULT_TEST_KEY", "default_test_val"), +/// ("DEFAULT_EXISTING_KEY", "loaded_from_env"), +/// ]); +/// ``` +pub fn assert_env_vars(vars: &[(&str, &str)]) { + for (key, expected) in vars { + assert_env_var(key, expected); + } +} + +/// Assert that an environment variable is set and has the expected value. +pub fn assert_env_var(key: &str, expected: &str) { + match env::var(key) { + Ok(actual) => assert_eq!( + expected, actual, + "\n\nFor Environment Variable `{}`:\n EXPECTED: `{}`\n ACTUAL: `{}`\n", + key, expected, actual + ), + Err(VarError::NotPresent) => panic!("env var `{}` not found", key), + Err(VarError::NotUnicode(val)) => panic!( + "env var `{}` currently has invalid unicode: `{}`", + key, + val.to_string_lossy() + ), + } +} + +/// Assert that an environment variable is not currently set. +pub fn assert_env_var_unset(key: &str) { + match env::var(key) { + Ok(actual) => panic!( + "env var `{}` should not be set, currently it is: `{}`", + key, actual + ), + Err(VarError::NotUnicode(val)) => panic!( + "env var `{}` should not be set, currently has invalid unicode: `{}`", + key, + val.to_string_lossy() + ), + _ => (), + } +} + +/// Assert that the default testing environment variables are not set. +pub fn assert_default_keys_unset() { + assert_env_var_unset(DEFAULT_EXISTING_KEY); + assert_env_var_unset(DEFAULT_TEST_KEY); +} diff --git a/dotenv/tests/integration/util/envfile.rs b/dotenv/tests/integration/util/envfile.rs index 4516823c..76f93d42 100644 --- a/dotenv/tests/integration/util/envfile.rs +++ b/dotenv/tests/integration/util/envfile.rs @@ -1,4 +1,39 @@ -/// Create envfile contents for testing +use super::*; + +#[inline(always)] +pub fn create_default_envfile() -> String { + format!( + "{}={}\n{}={}", + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE + ) +} + +/// Invalid due to missing `=` between key and value. +#[inline(always)] +pub fn create_invalid_envfile() -> String { + format!( + "{}{}\n{}{}", + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE + ) +} + +/// Create an envfile with custom key-value pairs. +/// +/// ## Example +/// +/// ```rust +/// let contents = create_custom_envfile(&[ +/// ("CUSTOM_KEY", "test_val"), +/// ("ANOTHER_KEY", "another_val"), +/// ]); +/// ``` +pub fn create_custom_envfile(env_vars: &[(&str, &str)]) -> String { + let mut efb = EnvFileBuilder::new(); + efb.add_vars(env_vars); + efb.into_owned_string() +} + +/// Advanced test-envfile constructor. /// /// Represented as bytes to allow for advanced manipulation and BOM testing. #[derive(Debug, Default)] diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index 98423b43..4bbcb065 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -1,96 +1,22 @@ -use std::env::{self, VarError}; - +mod assertions; mod envfile; mod testenv; mod tests; mod wrapper; -pub use envfile::EnvFileBuilder; -pub use testenv::{test_in_default_env, test_in_env, KeyVal, TestEnv}; +pub use assertions::*; +pub use envfile::*; +pub use testenv::*; pub use wrapper::*; /// Default key used in envfile -pub const TEST_KEY: &str = "TESTKEY"; +pub const DEFAULT_TEST_KEY: &str = "DEFAULT_TEST_KEY"; /// Default value used in envfile -pub const TEST_VALUE: &str = "test_val"; +pub const DEFAULT_TEST_VALUE: &str = "default_test_val"; /// Default existing key set before test is run -pub const TEST_EXISTING_KEY: &str = "TEST_EXISTING_KEY"; +pub const DEFAULT_EXISTING_KEY: &str = "DEFAULT_EXISTING_KEY"; /// Default existing value set before test is run -pub const TEST_EXISTING_VALUE: &str = "from_env"; +pub const DEFAULT_EXISTING_VALUE: &str = "loaded_from_env"; /// Default overriding value in envfile -pub const TEST_OVERRIDING_VALUE: &str = "from_file"; - -#[inline(always)] -pub fn create_default_envfile() -> String { - format!( - "{}={}\n{}={}", - TEST_KEY, TEST_VALUE, TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE - ) -} - -/// missing equals -#[inline(always)] -pub fn create_invalid_envfile() -> String { - format!( - "{}{}\n{}{}", - TEST_KEY, TEST_VALUE, TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE - ) -} - -pub fn create_custom_envfile(env_vars: &[(&str, &str)]) -> String { - let mut efb = EnvFileBuilder::new(); - efb.add_vars(env_vars); - efb.into_owned_string() -} - -/// Assert that a slice of environment variables are set and have the expected -/// values. -/// -/// ## Arguments -/// -/// * `vars` - A slice of key-expected value tuples -/// -/// ## Examples -/// -/// ```rust -/// assert_env_vars(&[("TESTKEY", "test_val"), ("TEST_EXISTING_KEY", "from_env")]); -/// ``` -pub fn assert_env_vars(vars: &[(&str, &str)]) { - for (key, expected) in vars { - assert_env_var(key, expected); - } -} - -/// Assert that an environment variable is set and has the expected value. -pub fn assert_env_var(key: &str, expected: &str) { - match env::var(key) { - Ok(actual) => assert_eq!( - expected, actual, - "\n\nFor Environment Variable `{}`:\n EXPECTED: `{}`\n ACTUAL: `{}`\n", - key, expected, actual - ), - Err(VarError::NotPresent) => panic!("env var `{}` not found", key), - Err(VarError::NotUnicode(val)) => panic!( - "env var `{}` currently has invalid unicode: `{}`", - key, - val.to_string_lossy() - ), - } -} - -/// Assert that an environment variable is not currently set. -pub fn assert_env_var_unset(key: &str) { - match env::var(key) { - Ok(actual) => panic!( - "env var `{}` should not be set, currently it is: `{}`", - key, actual - ), - Err(VarError::NotUnicode(val)) => panic!( - "env var `{}` should not be set, currently has invalid unicode: `{}`", - key, - val.to_string_lossy() - ), - _ => (), - } -} +pub const TEST_OVERRIDING_VALUE: &str = "loaded_from_file"; diff --git a/dotenv/tests/integration/util/testenv.rs b/dotenv/tests/integration/util/testenv.rs index f085f2f4..da1f195a 100644 --- a/dotenv/tests/integration/util/testenv.rs +++ b/dotenv/tests/integration/util/testenv.rs @@ -1,4 +1,4 @@ -use super::{create_default_envfile, TEST_EXISTING_KEY, TEST_EXISTING_VALUE}; +use super::{create_default_envfile, DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE}; use once_cell::sync::OnceCell; use std::{ collections::HashMap, @@ -68,15 +68,15 @@ where /// environment. /// /// The default testing environment sets an existing environment variable -/// `TEST_EXISTING_KEY`, which is set to `from_env`. It also creates a `.env` -/// file with the two lines: +/// `DEFAULT_EXISTING_KEY`, which is set to `loaded_from_env`. It also creates a +/// `.env` file with the two lines: /// /// ```ini -/// TESTKEY=test_val -/// TEST_EXISTING_KEY=from_file +/// DEFAULT_TEST_KEY=default_test_val +/// DEFAULT_EXISTING_KEY=loaded_from_file /// ``` /// -/// Notice that file has the potential to override `TEST_EXISTING_KEY` depending +/// Notice that file has the potential to override `DEFAULT_EXISTING_KEY` depending /// on the what's being tested. pub fn test_in_default_env(test: F) where @@ -255,8 +255,8 @@ impl Default for TestEnv { let temp_dir = tempdir().expect("create tempdir"); let work_dir = temp_dir.path().to_owned(); let env_vars = vec![KeyVal { - key: TEST_EXISTING_KEY.into(), - value: TEST_EXISTING_VALUE.into(), + key: DEFAULT_EXISTING_KEY.into(), + value: DEFAULT_EXISTING_VALUE.into(), }]; let envfile_contents = Some(create_default_envfile().into()); let envfile_path = work_dir.join(".env"); diff --git a/dotenv/tests/integration/util/tests/default_env.rs b/dotenv/tests/integration/util/tests/default_env.rs index de6e8e96..babbf1b1 100644 --- a/dotenv/tests/integration/util/tests/default_env.rs +++ b/dotenv/tests/integration/util/tests/default_env.rs @@ -3,8 +3,8 @@ use super::*; #[test] fn vars_state() { test_in_default_env(|| { - assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); - assert_env_var_unset(TEST_KEY); + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); + assert_env_var_unset(DEFAULT_TEST_KEY); }); } @@ -19,7 +19,7 @@ fn envfile_loaded_vars_state() { test_in_default_env(|| { dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var - assert_env_var(TEST_EXISTING_KEY, TEST_EXISTING_VALUE); - assert_env_var(TEST_KEY, TEST_VALUE); + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } diff --git a/dotenv/tests/integration/util/tests/envfile_builder.rs b/dotenv/tests/integration/util/tests/envfile_builder.rs index 4bd431a2..91f24a94 100644 --- a/dotenv/tests/integration/util/tests/envfile_builder.rs +++ b/dotenv/tests/integration/util/tests/envfile_builder.rs @@ -15,27 +15,27 @@ fn default_builds_empty() { #[test] fn add_key_empty_value() { let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, ""); - let expected = format!("{}=\n", TEST_KEY); + efb.add_key_value(DEFAULT_TEST_KEY, ""); + let expected = format!("{}=\n", DEFAULT_TEST_KEY); assert_contents_str(efb, &expected); } #[test] fn add_key_value() { let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); - let expected = format!("{}={}\n", TEST_KEY, TEST_VALUE); + efb.add_key_value(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); + let expected = format!("{}={}\n", DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); assert_contents_str(efb, &expected); } #[test] fn add_multiple_key_values() { let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); - efb.add_key_value(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); + efb.add_key_value(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); + efb.add_key_value(DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE); let expected = expected_envfile(&[ - (TEST_KEY, TEST_VALUE), - (TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE), + (DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE), + (DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE), ]); assert_contents_str(efb, &expected); } diff --git a/dotenv/tests/integration/util/tests/mod.rs b/dotenv/tests/integration/util/tests/mod.rs index 8f7c34ce..464593be 100644 --- a/dotenv/tests/integration/util/tests/mod.rs +++ b/dotenv/tests/integration/util/tests/mod.rs @@ -21,10 +21,5 @@ fn assert_envfile_exists_in_testenv(testenv: TestEnv) { } fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { - test_in_env(testenv, assert_default_keys_not_set); -} - -fn assert_default_keys_not_set() { - assert_env_var_unset(TEST_EXISTING_KEY); - assert_env_var_unset(TEST_KEY); + test_in_env(testenv, assert_default_keys_unset); } diff --git a/dotenv/tests/integration/util/tests/testenv.rs b/dotenv/tests/integration/util/tests/testenv.rs index fb24edfe..fbb2d01f 100644 --- a/dotenv/tests/integration/util/tests/testenv.rs +++ b/dotenv/tests/integration/util/tests/testenv.rs @@ -43,8 +43,8 @@ mod init_with_envfile { dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv - assert_env_var(TEST_EXISTING_KEY, TEST_OVERRIDING_VALUE); - assert_env_var(TEST_KEY, TEST_VALUE); + assert_env_var(DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE); + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } @@ -52,7 +52,7 @@ mod init_with_envfile { fn custom_envfile_vars_state() { let testenv = init_custom_envfile_testenv(); test_in_env(testenv, || { - assert_default_keys_not_set(); + assert_default_keys_unset(); for (key, _) in CUSTOM_VARS { assert_env_var_unset(key); } @@ -70,7 +70,7 @@ mod init_with_envfile { let testenv = init_custom_envfile_testenv(); test_in_env(testenv, || { dotenv_wrap().expect(DOTENV_EXPECT); - assert_default_keys_not_set(); + assert_default_keys_unset(); assert_env_vars(CUSTOM_VARS); }); } @@ -86,7 +86,7 @@ mod init_with_envfile { let testenv = init_empty_envfile_testenv(); test_in_env(testenv, || { dotenv_wrap().expect(DOTENV_EXPECT); - assert_default_keys_not_set(); + assert_default_keys_unset(); }); } @@ -101,7 +101,7 @@ mod init_with_envfile { let testenv = init_custom_bom_envfile_testenv(); test_in_env(testenv, || { dotenv_wrap().expect(DOTENV_EXPECT); - assert_env_var(TEST_KEY, TEST_VALUE); + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } @@ -121,7 +121,7 @@ mod init_with_envfile { fn init_custom_bom_envfile_testenv() -> TestEnv { let mut efb = EnvFileBuilder::new(); - efb.add_key_value(TEST_KEY, TEST_VALUE); + efb.add_key_value(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); efb.insert_utf8_bom(); let envfile = efb.into_owned_string(); TestEnv::init_with_envfile(envfile) From 799cfb66c4f664cb101de104d8e1a1a5004a2a68 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Sun, 14 Jul 2024 12:08:11 +0100 Subject: [PATCH 13/44] Move test harness to separate crate Allow both dotenvy and dotenvy_macro to make use of the test harness --- Cargo.toml | 3 ++- dotenv/Cargo.toml | 2 +- dotenv/tests/integration/dotenv.rs | 2 +- dotenv/tests/integration/main.rs | 2 -- test_util/Cargo.toml | 20 +++++++++++++++++++ .../util => test_util/src}/assertions.rs | 5 +++-- .../util => test_util/src}/envfile.rs | 4 +++- .../util/mod.rs => test_util/src/lib.rs | 4 +++- .../util => test_util/src}/testenv.rs | 0 .../src}/tests/default_env.rs | 0 .../src}/tests/envfile_builder.rs | 0 .../util => test_util/src}/tests/mod.rs | 0 .../util => test_util/src}/tests/testenv.rs | 0 .../util => test_util/src}/wrapper.rs | 0 14 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 test_util/Cargo.toml rename {dotenv/tests/integration/util => test_util/src}/assertions.rs (95%) rename {dotenv/tests/integration/util => test_util/src}/envfile.rs (96%) rename dotenv/tests/integration/util/mod.rs => test_util/src/lib.rs (97%) rename {dotenv/tests/integration/util => test_util/src}/testenv.rs (100%) rename {dotenv/tests/integration/util => test_util/src}/tests/default_env.rs (100%) rename {dotenv/tests/integration/util => test_util/src}/tests/envfile_builder.rs (100%) rename {dotenv/tests/integration/util => test_util/src}/tests/mod.rs (100%) rename {dotenv/tests/integration/util => test_util/src}/tests/testenv.rs (100%) rename {dotenv/tests/integration/util => test_util/src}/wrapper.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 68bfba33..d5ca61b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ [workspace] +resolver = "2" -members = ["dotenv", "dotenv_codegen"] +members = ["dotenv", "dotenv_codegen", "test_util"] diff --git a/dotenv/Cargo.toml b/dotenv/Cargo.toml index 51b32a3e..84c41eb8 100644 --- a/dotenv/Cargo.toml +++ b/dotenv/Cargo.toml @@ -29,8 +29,8 @@ required-features = ["cli"] clap = { version = "4.3.11", optional = true } [dev-dependencies] +dotenvy_test_util = { path = "../test_util", version = "0.1.0" } tempfile = "3.3.0" -once_cell = "1.16.0" [features] cli = ["clap"] diff --git a/dotenv/tests/integration/dotenv.rs b/dotenv/tests/integration/dotenv.rs index a47fc423..3c95d826 100644 --- a/dotenv/tests/integration/dotenv.rs +++ b/dotenv/tests/integration/dotenv.rs @@ -1,4 +1,4 @@ -use crate::util::*; +use dotenvy_test_util::*; #[test] fn dotenv_ok_default_env() { diff --git a/dotenv/tests/integration/main.rs b/dotenv/tests/integration/main.rs index a61ebe72..8c74edd4 100644 --- a/dotenv/tests/integration/main.rs +++ b/dotenv/tests/integration/main.rs @@ -1,3 +1 @@ -pub mod util; - mod dotenv; diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml new file mode 100644 index 00000000..8368d21e --- /dev/null +++ b/test_util/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dotenvy_test_util" +version = "0.1.0" +authors = [ + "Christopher Morton ", +] +description = "Test utilities for dotenvy" +homepage = "https://github.com/allan2/dotenvy" +readme = "README.md" +keywords = ["dotenv", "testing", "utility", "util", "harness"] +categories = ["development-tools", "development-tools::testing"] +license = "MIT" +repository = "https://github.com/allan2/dotenvy" +edition = "2021" +rust-version = "1.68.0" + +[dependencies] +dotenvy = { path = "../dotenv", version = "0.15.7" } +tempfile = "3.3.0" +once_cell = "1.16.0" diff --git a/dotenv/tests/integration/util/assertions.rs b/test_util/src/assertions.rs similarity index 95% rename from dotenv/tests/integration/util/assertions.rs rename to test_util/src/assertions.rs index fbaff16b..fa353d0c 100644 --- a/dotenv/tests/integration/util/assertions.rs +++ b/test_util/src/assertions.rs @@ -8,9 +8,10 @@ use std::env::{self, VarError}; /// /// * `vars` - A slice of key-expected value tuples /// -/// ## Examples +/// ## Example /// -/// ```rust +/// ```no_run +/// # use dotenvy_test_util::assert_env_vars; /// assert_env_vars(&[ /// ("DEFAULT_TEST_KEY", "default_test_val"), /// ("DEFAULT_EXISTING_KEY", "loaded_from_env"), diff --git a/dotenv/tests/integration/util/envfile.rs b/test_util/src/envfile.rs similarity index 96% rename from dotenv/tests/integration/util/envfile.rs rename to test_util/src/envfile.rs index 76f93d42..57f2675d 100644 --- a/dotenv/tests/integration/util/envfile.rs +++ b/test_util/src/envfile.rs @@ -21,11 +21,13 @@ pub fn create_invalid_envfile() -> String { /// /// ## Example /// -/// ```rust +/// ```no_run +/// # use dotenvy_test_util::create_custom_envfile; /// let contents = create_custom_envfile(&[ /// ("CUSTOM_KEY", "test_val"), /// ("ANOTHER_KEY", "another_val"), /// ]); +/// assert_eq!(contents, "CUSTOM_KEY=test_val\nANOTHER_KEY=another_val\n"); /// ``` pub fn create_custom_envfile(env_vars: &[(&str, &str)]) -> String { let mut efb = EnvFileBuilder::new(); diff --git a/dotenv/tests/integration/util/mod.rs b/test_util/src/lib.rs similarity index 97% rename from dotenv/tests/integration/util/mod.rs rename to test_util/src/lib.rs index 4bbcb065..33c10833 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/test_util/src/lib.rs @@ -1,9 +1,11 @@ mod assertions; mod envfile; mod testenv; -mod tests; mod wrapper; +#[cfg(test)] +mod tests; + pub use assertions::*; pub use envfile::*; pub use testenv::*; diff --git a/dotenv/tests/integration/util/testenv.rs b/test_util/src/testenv.rs similarity index 100% rename from dotenv/tests/integration/util/testenv.rs rename to test_util/src/testenv.rs diff --git a/dotenv/tests/integration/util/tests/default_env.rs b/test_util/src/tests/default_env.rs similarity index 100% rename from dotenv/tests/integration/util/tests/default_env.rs rename to test_util/src/tests/default_env.rs diff --git a/dotenv/tests/integration/util/tests/envfile_builder.rs b/test_util/src/tests/envfile_builder.rs similarity index 100% rename from dotenv/tests/integration/util/tests/envfile_builder.rs rename to test_util/src/tests/envfile_builder.rs diff --git a/dotenv/tests/integration/util/tests/mod.rs b/test_util/src/tests/mod.rs similarity index 100% rename from dotenv/tests/integration/util/tests/mod.rs rename to test_util/src/tests/mod.rs diff --git a/dotenv/tests/integration/util/tests/testenv.rs b/test_util/src/tests/testenv.rs similarity index 100% rename from dotenv/tests/integration/util/tests/testenv.rs rename to test_util/src/tests/testenv.rs diff --git a/dotenv/tests/integration/util/wrapper.rs b/test_util/src/wrapper.rs similarity index 100% rename from dotenv/tests/integration/util/wrapper.rs rename to test_util/src/wrapper.rs From cddf9bddeebf6ca45eb6c905c97400d9919c316d Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Sun, 14 Jul 2024 12:28:59 +0100 Subject: [PATCH 14/44] Fix const naming convension --- test_util/src/envfile.rs | 4 ++-- test_util/src/lib.rs | 2 +- test_util/src/tests/envfile_builder.rs | 4 ++-- test_util/src/tests/testenv.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test_util/src/envfile.rs b/test_util/src/envfile.rs index 57f2675d..7354327c 100644 --- a/test_util/src/envfile.rs +++ b/test_util/src/envfile.rs @@ -4,7 +4,7 @@ use super::*; pub fn create_default_envfile() -> String { format!( "{}={}\n{}={}", - DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE ) } @@ -13,7 +13,7 @@ pub fn create_default_envfile() -> String { pub fn create_invalid_envfile() -> String { format!( "{}{}\n{}{}", - DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE ) } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 33c10833..640e0db6 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -21,4 +21,4 @@ pub const DEFAULT_EXISTING_KEY: &str = "DEFAULT_EXISTING_KEY"; /// Default existing value set before test is run pub const DEFAULT_EXISTING_VALUE: &str = "loaded_from_env"; /// Default overriding value in envfile -pub const TEST_OVERRIDING_VALUE: &str = "loaded_from_file"; +pub const DEFAULT_OVERRIDING_VALUE: &str = "loaded_from_file"; diff --git a/test_util/src/tests/envfile_builder.rs b/test_util/src/tests/envfile_builder.rs index 91f24a94..cd720fed 100644 --- a/test_util/src/tests/envfile_builder.rs +++ b/test_util/src/tests/envfile_builder.rs @@ -32,10 +32,10 @@ fn add_key_value() { fn add_multiple_key_values() { let mut efb = EnvFileBuilder::new(); efb.add_key_value(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); - efb.add_key_value(DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE); + efb.add_key_value(DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE); let expected = expected_envfile(&[ (DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE), - (DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE), + (DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE), ]); assert_contents_str(efb, &expected); } diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index fbb2d01f..5c23f2cd 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -43,7 +43,7 @@ mod init_with_envfile { dotenv_wrap().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv - assert_env_var(DEFAULT_EXISTING_KEY, TEST_OVERRIDING_VALUE); + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } From 1a93d8698e50b90e2d29b89142986b3728ea458d Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 12:23:01 +0100 Subject: [PATCH 15/44] Refactor wrappers into module --- test_util/src/lib.rs | 3 +-- test_util/src/tests/default_env.rs | 2 +- test_util/src/tests/mod.rs | 2 +- test_util/src/tests/testenv.rs | 10 ++++---- test_util/src/{wrapper.rs => wrap.rs} | 35 ++++++++++++++++----------- 5 files changed, 29 insertions(+), 23 deletions(-) rename test_util/src/{wrapper.rs => wrap.rs} (51%) diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 640e0db6..2a3eb9da 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -1,7 +1,7 @@ mod assertions; mod envfile; mod testenv; -mod wrapper; +pub mod wrap; #[cfg(test)] mod tests; @@ -9,7 +9,6 @@ mod tests; pub use assertions::*; pub use envfile::*; pub use testenv::*; -pub use wrapper::*; /// Default key used in envfile pub const DEFAULT_TEST_KEY: &str = "DEFAULT_TEST_KEY"; diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index babbf1b1..fe54e76c 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -17,7 +17,7 @@ fn envfile_exists() { #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { - dotenv_wrap().expect(DOTENV_EXPECT); + wrap::dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index 464593be..1b27599c 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -16,7 +16,7 @@ fn assert_envfile_exists_in_testenv(testenv: TestEnv) { test_in_env(testenv, || { assert!(envfile_path.exists()); - assert!(dotenv_wrap().is_ok()); + assert!(wrap::dotenv().is_ok()); }); } diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 5c23f2cd..518bb547 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -16,7 +16,7 @@ mod init { test_in_env(init_testenv, || { assert!(!envfile_path.exists()); - assert!(dotenv_wrap().is_err()); + assert!(wrap::dotenv().is_err()); }); } } @@ -40,7 +40,7 @@ mod init_with_envfile { fn default_envfile_loaded_vars_state() { let testenv = init_default_envfile_testenv(); test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); + wrap::dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE); @@ -69,7 +69,7 @@ mod init_with_envfile { fn custom_envfile_loaded_vars_state() { let testenv = init_custom_envfile_testenv(); test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); + wrap::dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); assert_env_vars(CUSTOM_VARS); }); @@ -85,7 +85,7 @@ mod init_with_envfile { fn empty_envfile_loaded_vars_state() { let testenv = init_empty_envfile_testenv(); test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); + wrap::dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); }); } @@ -100,7 +100,7 @@ mod init_with_envfile { fn custom_bom_envfile_loaded_vars_state() { let testenv = init_custom_bom_envfile_testenv(); test_in_env(testenv, || { - dotenv_wrap().expect(DOTENV_EXPECT); + wrap::dotenv().expect(DOTENV_EXPECT); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } diff --git a/test_util/src/wrapper.rs b/test_util/src/wrap.rs similarity index 51% rename from test_util/src/wrapper.rs rename to test_util/src/wrap.rs index 116791f7..04d5a03f 100644 --- a/test_util/src/wrapper.rs +++ b/test_util/src/wrap.rs @@ -1,6 +1,13 @@ //! Wrappers for the `dotenvy` API. //! //! If the `dotenvy` API changes, only this module needs to be updated. +//! +//! ## Example +//! +//! ```no_run +//! use dotenvy_test_util::wrap; +//! wrap::dotenv().expect("Failed to load .env file"); +//! ``` use dotenvy::{self, Iter, Result}; use std::env::Vars; @@ -10,71 +17,71 @@ use std::io; use std::path::{Path, PathBuf}; #[inline(always)] -pub fn var_wrap>(key: K) -> Result { +pub fn var>(key: K) -> Result { dotenvy::var(key) } #[inline(always)] -pub fn vars_wrap() -> Vars { +pub fn vars() -> Vars { dotenvy::vars() } #[inline(always)] -pub fn from_path_wrap>(path: P) -> Result<()> { +pub fn from_path>(path: P) -> Result<()> { dotenvy::from_path(path) } #[inline(always)] -pub fn from_path_override_wrap>(path: P) -> Result<()> { +pub fn from_path_override>(path: P) -> Result<()> { dotenvy::from_path_override(path) } #[inline(always)] -pub fn from_path_iter_wrap>(path: P) -> Result> { +pub fn from_path_iter>(path: P) -> Result> { dotenvy::from_path_iter(path) } #[inline(always)] -pub fn from_filename_wrap>(filename: P) -> Result { +pub fn from_filename>(filename: P) -> Result { dotenvy::from_filename(filename) } #[inline(always)] -pub fn from_filename_override_wrap>(filename: P) -> Result { +pub fn from_filename_override>(filename: P) -> Result { dotenvy::from_filename_override(filename) } #[inline(always)] -pub fn from_filename_iter_wrap>(filename: P) -> Result> { +pub fn from_filename_iter>(filename: P) -> Result> { dotenvy::from_filename_iter(filename) } #[inline(always)] -pub fn from_read_wrap(reader: R) -> Result<()> { +pub fn from_read(reader: R) -> Result<()> { dotenvy::from_read(reader) } #[inline(always)] -pub fn from_read_override_wrap(reader: R) -> Result<()> { +pub fn from_read_override(reader: R) -> Result<()> { dotenvy::from_read_override(reader) } #[inline(always)] -pub fn from_read_iter_wrap(reader: R) -> Iter { +pub fn from_read_iter(reader: R) -> Iter { dotenvy::from_read_iter(reader) } #[inline(always)] -pub fn dotenv_wrap() -> Result { +pub fn dotenv() -> Result { dotenvy::dotenv() } #[inline(always)] -pub fn dotenv_override_wrap() -> Result { +pub fn dotenv_override() -> Result { dotenvy::dotenv_override() } #[inline(always)] -pub fn dotenv_iter_wrap() -> Result> { +pub fn dotenv_iter() -> Result> { dotenvy::dotenv_iter() } From 538e196d3d9114a45b07432c74834a931e31b70f Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 12:23:22 +0100 Subject: [PATCH 16/44] Flesh out test_util docs --- test_util/src/assertions.rs | 10 ++++---- test_util/src/envfile.rs | 13 +++++++--- test_util/src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++ test_util/src/testenv.rs | 4 +-- 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/test_util/src/assertions.rs b/test_util/src/assertions.rs index fa353d0c..f9fa9e68 100644 --- a/test_util/src/assertions.rs +++ b/test_util/src/assertions.rs @@ -1,12 +1,12 @@ use super::*; use std::env::{self, VarError}; -/// Assert that a slice of environment variables are set and have the expected +/// Assert multiple environment variables are set and have the expected /// values. /// /// ## Arguments /// -/// * `vars` - A slice of key-expected value tuples +/// * `vars` - A slice of `(key, expected_value)` tuples /// /// ## Example /// @@ -23,7 +23,7 @@ pub fn assert_env_vars(vars: &[(&str, &str)]) { } } -/// Assert that an environment variable is set and has the expected value. +/// Assert environment variable is set and has the expected value. pub fn assert_env_var(key: &str, expected: &str) { match env::var(key) { Ok(actual) => assert_eq!( @@ -40,7 +40,7 @@ pub fn assert_env_var(key: &str, expected: &str) { } } -/// Assert that an environment variable is not currently set. +/// Assert environment variable is not currently set. pub fn assert_env_var_unset(key: &str) { match env::var(key) { Ok(actual) => panic!( @@ -56,7 +56,7 @@ pub fn assert_env_var_unset(key: &str) { } } -/// Assert that the default testing environment variables are not set. +/// Assert default testing environment variables are not set. pub fn assert_default_keys_unset() { assert_env_var_unset(DEFAULT_EXISTING_KEY); assert_env_var_unset(DEFAULT_TEST_KEY); diff --git a/test_util/src/envfile.rs b/test_util/src/envfile.rs index 7354327c..39da4317 100644 --- a/test_util/src/envfile.rs +++ b/test_util/src/envfile.rs @@ -1,6 +1,11 @@ use super::*; #[inline(always)] +/// Create the default envfile contents. +/// +/// [`DEFAULT_TEST_KEY`] set as [`DEFAULT_TEST_VALUE`] +/// +/// [`DEFAULT_EXISTING_KEY`] set as [`DEFAULT_OVERRIDING_VALUE`] pub fn create_default_envfile() -> String { format!( "{}={}\n{}={}", @@ -59,7 +64,7 @@ impl EnvFileBuilder { /// /// ## Panics /// - /// If the contents of the builder are not valid UTF-8. + /// If the contents of the builder is not valid UTF-8. pub fn build_string(&self) -> String { String::from_utf8(self.contents.clone()).expect("valid UTF-8") } @@ -73,7 +78,7 @@ impl EnvFileBuilder { /// /// ## Panics /// - /// If the contents of the builder are not valid UTF-8. + /// If the contents of the builder is not valid UTF-8. pub fn into_owned_string(self) -> String { String::from_utf8(self.contents).expect("valid UTF-8") } @@ -83,7 +88,9 @@ impl EnvFileBuilder { &self.contents } - /// Add + /// Add a slice of key-value pairs, separated by newlines. + /// + /// Includes a trailing newline. pub fn add_vars(&mut self, env_vars: &[(&str, &str)]) -> &mut Self { let mut many = String::new(); for (key, value) in env_vars { diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 2a3eb9da..4d2c78c3 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -1,3 +1,53 @@ +//! Test environment setup, assertions and helpers. +//! +//! Setup a [`TestEnv`] and run your tests via [`test_in_env`]. The environment +//! can be tweaked with: +//! +//! - pre-existing environment variables, +//! - different directory layouts, +//! - custom `.env` file contents, +//! - custom envfile name/path. +//! +//! Use the `create_` helper functions, such as [`create_custom_envfile`], to +//! generate the `.env` file contents. If you need more control of the +//! envfile's bytes, use the [`EnvFileBuilder`]. +//! +//! In your tests, call the [`dotenvy`] API via the [`wrap`] module to keep any +//! API changes in one place. Then make use of the `assert_` helpers, such as +//! [`assert_env_var`] and [`assert_env_var_unset`], to check the state of the +//! environment. +//! +//! ## Example +//! +//! ```no_run +//! use dotenvy_test_util::*; +//! +//! const EXISTING_KEY: &str = "TEST_KEY"; +//! const EXISTING_VAL: &str = "test_val"; +//! const OVERRIDING_VAL: &str = "overriding_val"; +//! +//! #[test] +//! fn dotenv_override_existing_key() { +//! // setup testing environment +//! let mut testenv = TestEnv::init(); +//! +//! // with an existing environment variable +//! testenv.set_env_var(EXISTING_KEY, EXISTING_VAL); +//! +//! // with an envfile that overrides it +//! testenv.set_envfile_contents( +//! create_custom_envfile(&[(EXISTING_KEY, OVERRIDING_VAL)]), +//! ); +//! +//! // execute the closure in the testing environment +//! test_in_env(testenv, || { +//! wrap::dotenv_override().expect(".env should be loaded"); +//! assert_env_var(EXISTING_KEY, OVERRIDING_VAL); +//! }); +//! // any changes to environment variables will be reset for other tests +//! } +//! ``` + mod assertions; mod envfile; mod testenv; diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index da1f195a..0846b1fb 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -38,8 +38,8 @@ pub struct TestEnv { /// Simple key value struct for representing environment variables #[derive(Debug, Clone)] pub struct KeyVal { - key: String, - value: String, + pub key: String, + pub value: String, } /// Run a test closure within a test environment. From 31cf0746afa90c91dad553fac76c1d4d5be6ba16 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 12:56:45 +0100 Subject: [PATCH 17/44] Add more than one envfile to TestEnv --- test_util/src/testenv.rs | 118 ++++++++++++----------------- test_util/src/tests/default_env.rs | 2 +- test_util/src/tests/mod.rs | 13 +++- test_util/src/tests/testenv.rs | 10 +-- 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 0846b1fb..542f3525 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -31,8 +31,14 @@ pub struct TestEnv { temp_dir: TempDir, work_dir: PathBuf, env_vars: Vec, - envfile_contents: Option>, - envfile_path: PathBuf, + envfiles: Vec, +} + +#[derive(Debug)] +/// Simple path and byte contents representing a `.env` file +pub struct EnvFile { + pub path: PathBuf, + pub contents: Vec, } /// Simple key value struct for representing environment variables @@ -95,50 +101,35 @@ impl TestEnv { pub fn init() -> Self { let tempdir = tempdir().expect("create tempdir"); let work_dir = tempdir.path().to_owned(); - let envfile_path = work_dir.join(".env"); Self { temp_dir: tempdir, work_dir, env_vars: Default::default(), - envfile_contents: None, - envfile_path, + envfiles: vec![], } } - /// Testing environment with custom envfile_contents. + /// Testing environment with custom envfile contents. /// /// No pre-existing env_vars set. The envfile_name is set to `.env`. The /// working directory is the created temporary directory. pub fn init_with_envfile(contents: impl Into>) -> Self { let mut test_env = Self::init(); - test_env.set_envfile_contents(contents); + test_env.add_envfile(".env", contents); test_env } - /// Change the absolute path to the envfile. - pub fn set_envfile_path(&mut self, path: PathBuf) -> &mut Self { - self.envfile_path = path; - self - } - - /// Specify the contents of the envfile. - /// - /// If this is the only change to the [`TestEnv`] being made, use - /// [`init_with_envfile`](TestEnv::init_with_envfile). - /// - /// Setting it to an empty string will cause an empty envfile to be created - pub fn set_envfile_contents(&mut self, contents: impl Into>) -> &mut Self { - let contents = contents.into(); - self.envfile_contents = Some(contents); - self - } - - /// Set the working directory the test will run from. - /// - /// The default is the created temporary directory. This method is useful if - /// you wish to run a test from a subdirectory or somewhere else. - pub fn set_work_dir(&mut self, path: PathBuf) -> &mut Self { - self.work_dir = path; + /// Add an individual envfile. + pub fn add_envfile(&mut self, path: P, contents: C) -> &mut Self + where + P: Into, + C: Into>, + { + let envfile = EnvFile { + path: self.temp_dir.path().join(path.into()), + contents: contents.into(), + }; + self.envfiles.push(envfile); self } @@ -154,7 +145,7 @@ impl TestEnv { self } - /// Set the pre-existing environment variables. + /// Set all the pre-existing environment variables. /// /// These variables will get added to the process' environment before the /// test is run. This overrides any previous env vars added to the @@ -167,7 +158,7 @@ impl TestEnv { self } - /// Set the pre-existing environment variables using [`str`] tuples. + /// Set all the pre-existing environment variables using [`str`] tuples. /// /// These variables will get added to the process' environment before the /// test is run. This overrides any previous env vars added to the @@ -187,6 +178,15 @@ impl TestEnv { self } + /// Set the working directory the test will run from. + /// + /// The default is the created temporary directory. This method is useful if + /// you wish to run a test from a subdirectory or somewhere else. + pub fn set_work_dir(&mut self, path: PathBuf) -> &mut Self { + self.work_dir = path; + self + } + /// Create a child folder within the temporary directory. /// /// This will not change the working directory the test is run in, or where @@ -207,46 +207,24 @@ impl TestEnv { child_dir } - /// Get a reference to the path of the temporary directory. + /// Reference to the path of the temporary directory. pub fn temp_path(&self) -> &Path { self.temp_dir.path() } - /// Get a reference to the working directory the test will be run from. + /// Reference to the working directory the test will be run from. pub fn work_dir(&self) -> &Path { &self.work_dir } - /// Get a reference to environment variables that will be set **before** - /// the test. + /// Reference to environment variables that will be set **before** the test. pub fn env_vars(&self) -> &[KeyVal] { &self.env_vars } - /// Get a reference to the bytes that will be placed in the envfile. - /// - /// If `None` is returned, an envfile will not be created - pub fn envfile_contents_as_bytes(&self) -> Option<&[u8]> { - self.envfile_contents.as_deref() - } - - /// Get a reference to the string that will be placed in the envfile. - /// - /// If `None` is returned, an envfile will not be created - /// - /// ## Panics - /// - /// This will panic if the envfile contents are not valid UTF-8 - pub fn envfile_contents_as_str(&self) -> Option<&str> { - self.envfile_contents_as_bytes().map(|bytes| { - let out = std::str::from_utf8(bytes).expect("valid UTF-8"); - Some(out) - })? - } - - /// Get a reference to the path of the envfile. - pub fn envfile_path(&self) -> &Path { - &self.envfile_path + /// Get a reference to the environment files that will created + pub fn envfiles(&self) -> &[EnvFile] { + &self.envfiles } } @@ -258,14 +236,15 @@ impl Default for TestEnv { key: DEFAULT_EXISTING_KEY.into(), value: DEFAULT_EXISTING_VALUE.into(), }]; - let envfile_contents = Some(create_default_envfile().into()); - let envfile_path = work_dir.join(".env"); + let envfiles = vec![EnvFile { + path: work_dir.join(".env"), + contents: create_default_envfile().into(), + }]; Self { temp_dir, work_dir, env_vars, - envfile_contents, - envfile_path, + envfiles, } } } @@ -311,13 +290,12 @@ fn reset_env(original_env: &EnvMap) { /// /// Writes the envfile, sets the working directory, and sets environment vars. fn create_env(test_env: &TestEnv) { - // only create the envfile if its contents has been set - if let Some(contents) = test_env.envfile_contents_as_bytes() { - create_envfile(&test_env.envfile_path, contents); - } - env::set_current_dir(&test_env.work_dir).expect("setting working directory"); + for EnvFile { path, contents } in &test_env.envfiles { + create_envfile(path, contents); + } + for KeyVal { key, value } in &test_env.env_vars { env::set_var(key, value) } diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index fe54e76c..858defcc 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -11,7 +11,7 @@ fn vars_state() { #[test] fn envfile_exists() { let testenv = TestEnv::default(); - assert_envfile_exists_in_testenv(testenv); + assert_envfiles_exist_in_testenv(testenv); } #[test] diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index 1b27599c..5c2a9803 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -11,12 +11,17 @@ const CUSTOM_VARS: &[(&str, &str)] = &[ const DOTENV_EXPECT: &str = "TestEnv should have .env file"; -fn assert_envfile_exists_in_testenv(testenv: TestEnv) { - let envfile_path = testenv.envfile_path().to_owned(); +fn assert_envfiles_exist_in_testenv(testenv: TestEnv) { + let paths = testenv + .envfiles() + .iter() + .map(|envfile| envfile.path.clone()) + .collect::>(); test_in_env(testenv, || { - assert!(envfile_path.exists()); - assert!(wrap::dotenv().is_ok()); + for path in paths { + assert!(path.exists()); + } }); } diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 518bb547..9976e9ca 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -12,7 +12,7 @@ mod init { #[test] fn no_envfile() { let init_testenv = TestEnv::init(); - let envfile_path = init_testenv.envfile_path().to_owned(); + let envfile_path = init_testenv.temp_path().join(".env"); test_in_env(init_testenv, || { assert!(!envfile_path.exists()); @@ -33,7 +33,7 @@ mod init_with_envfile { #[test] fn default_envfile_exists() { let testenv = init_default_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); + assert_envfiles_exist_in_testenv(testenv); } #[test] @@ -62,7 +62,7 @@ mod init_with_envfile { #[test] fn custom_envfile_exists() { let testenv = init_custom_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); + assert_envfiles_exist_in_testenv(testenv); } #[test] @@ -78,7 +78,7 @@ mod init_with_envfile { #[test] fn empty_envfile_exists() { let testenv = init_empty_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); + assert_envfiles_exist_in_testenv(testenv); } #[test] @@ -93,7 +93,7 @@ mod init_with_envfile { #[test] fn custom_bom_envfile_exists() { let testenv = init_custom_bom_envfile_testenv(); - assert_envfile_exists_in_testenv(testenv); + assert_envfiles_exist_in_testenv(testenv); } #[test] From 443ab304cd999e7c0d466b0f2f58e9275d5d01be Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 13:04:28 +0100 Subject: [PATCH 18/44] Fix parameter ownership in methods --- test_util/src/testenv.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 542f3525..81fc8a04 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -137,10 +137,14 @@ impl TestEnv { /// /// This adds more pre-existing environment variables to the process before /// any tests are run. - pub fn add_env_var(&mut self, key: impl ToString, value: impl ToString) -> &mut Self { + pub fn add_env_var(&mut self, key: K, value: V) -> &mut Self + where + K: Into, + V: Into, + { self.env_vars.push(KeyVal { - key: key.to_string(), - value: value.to_string(), + key: key.into(), + value: value.into(), }); self } @@ -182,8 +186,8 @@ impl TestEnv { /// /// The default is the created temporary directory. This method is useful if /// you wish to run a test from a subdirectory or somewhere else. - pub fn set_work_dir(&mut self, path: PathBuf) -> &mut Self { - self.work_dir = path; + pub fn set_work_dir(&mut self, path: impl Into) -> &mut Self { + self.work_dir = path.into(); self } @@ -193,18 +197,17 @@ impl TestEnv { /// the envfile is created. /// /// Will create parent directories if they are missing. - pub fn add_child_dir_all(&self, rel_path: impl AsRef) -> PathBuf { - let rel_path = rel_path.as_ref(); - let child_dir = self.temp_path().join(rel_path); - if let Err(err) = fs::create_dir_all(&child_dir) { + pub fn add_child_dir_all(&self, path: impl AsRef) { + let path = path.as_ref(); + let child_dir = self.temp_path().join(path); + if let Err(err) = fs::create_dir_all(child_dir) { panic!( "unable to create child directory: `{}` in `{}`: {}", self.temp_path().display(), - rel_path.display(), + path.display(), err ); } - child_dir } /// Reference to the path of the temporary directory. @@ -288,7 +291,7 @@ fn reset_env(original_env: &EnvMap) { /// Create an environment to run tests in. /// -/// Writes the envfile, sets the working directory, and sets environment vars. +/// Writes the envfiles, sets the working directory, and sets environment vars. fn create_env(test_env: &TestEnv) { env::set_current_dir(&test_env.work_dir).expect("setting working directory"); From 972162cd2177d31a4f5a711e5e3b73d3b1ae1db0 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 13:31:05 +0100 Subject: [PATCH 19/44] Fix testenv lifetime --- test_util/src/testenv.rs | 10 +++++----- test_util/src/tests/default_env.rs | 2 +- test_util/src/tests/mod.rs | 30 +++++++++++++++++++++--------- test_util/src/tests/testenv.rs | 24 ++++++++++++------------ 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 81fc8a04..b7fb68c7 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -34,7 +34,7 @@ pub struct TestEnv { envfiles: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Simple path and byte contents representing a `.env` file pub struct EnvFile { pub path: PathBuf, @@ -52,7 +52,7 @@ pub struct KeyVal { /// /// Resets the environment variables, loads the [`TestEnv`], then runs the test /// closure. Ensures only one thread has access to the process environment. -pub fn test_in_env(test_env: TestEnv, test: F) +pub fn test_in_env(test_env: &TestEnv, test: F) where F: FnOnce(), { @@ -62,9 +62,9 @@ where let original_env = locker.lock().unwrap_or_else(PoisonError::into_inner); // we reset the environment anyway upon acquiring the lock reset_env(&original_env); - create_env(&test_env); + create_env(test_env); test(); - // drop the lock and the `TestEnv` - should delete the tempdir + // drop the lock } /// Run a test closure within the default test environment. @@ -89,7 +89,7 @@ where F: FnOnce(), { let test_env = TestEnv::default(); - test_in_env(test_env, test); + test_in_env(&test_env, test); } impl TestEnv { diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index 858defcc..f9d5b09b 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -11,7 +11,7 @@ fn vars_state() { #[test] fn envfile_exists() { let testenv = TestEnv::default(); - assert_envfiles_exist_in_testenv(testenv); + assert_envfiles_in_testenv(&testenv); } #[test] diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index 5c2a9803..dcd55885 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use super::*; mod default_env; @@ -11,20 +13,30 @@ const CUSTOM_VARS: &[(&str, &str)] = &[ const DOTENV_EXPECT: &str = "TestEnv should have .env file"; -fn assert_envfiles_exist_in_testenv(testenv: TestEnv) { - let paths = testenv - .envfiles() - .iter() - .map(|envfile| envfile.path.clone()) - .collect::>(); +fn assert_envfiles_in_testenv(testenv: &TestEnv) { + let files = testenv.envfiles(); test_in_env(testenv, || { - for path in paths { - assert!(path.exists()); + for EnvFile { path, contents } in files { + assert_envfile(path, contents); } }); } -fn assert_default_keys_not_set_in_testenv(testenv: TestEnv) { +fn assert_envfile(path: &Path, expected: &[u8]) { + assert!(path.exists(), "{} should exist in testenv", path.display()); + + let actual = std::fs::read(path) + .unwrap_or_else(|e| panic!("failed to read {} in testenv: {}", path.display(), e)); + + assert_eq!( + expected, + &actual, + "{} has incorrect contents", + path.display() + ); +} + +fn assert_default_keys_not_set_in_testenv(testenv: &TestEnv) { test_in_env(testenv, assert_default_keys_unset); } diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 9976e9ca..17d7868d 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -6,7 +6,7 @@ mod init { #[test] fn vars_state() { let init_testenv = TestEnv::init(); - assert_default_keys_not_set_in_testenv(init_testenv); + assert_default_keys_not_set_in_testenv(&init_testenv); } #[test] @@ -14,7 +14,7 @@ mod init { let init_testenv = TestEnv::init(); let envfile_path = init_testenv.temp_path().join(".env"); - test_in_env(init_testenv, || { + test_in_env(&init_testenv, || { assert!(!envfile_path.exists()); assert!(wrap::dotenv().is_err()); }); @@ -27,19 +27,19 @@ mod init_with_envfile { #[test] fn default_envfile_vars_state() { let testenv = init_default_envfile_testenv(); - assert_default_keys_not_set_in_testenv(testenv); + assert_default_keys_not_set_in_testenv(&testenv); } #[test] fn default_envfile_exists() { let testenv = init_default_envfile_testenv(); - assert_envfiles_exist_in_testenv(testenv); + assert_envfiles_in_testenv(&testenv); } #[test] fn default_envfile_loaded_vars_state() { let testenv = init_default_envfile_testenv(); - test_in_env(testenv, || { + test_in_env(&testenv, || { wrap::dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv @@ -51,7 +51,7 @@ mod init_with_envfile { #[test] fn custom_envfile_vars_state() { let testenv = init_custom_envfile_testenv(); - test_in_env(testenv, || { + test_in_env(&testenv, || { assert_default_keys_unset(); for (key, _) in CUSTOM_VARS { assert_env_var_unset(key); @@ -62,13 +62,13 @@ mod init_with_envfile { #[test] fn custom_envfile_exists() { let testenv = init_custom_envfile_testenv(); - assert_envfiles_exist_in_testenv(testenv); + assert_envfiles_in_testenv(&testenv); } #[test] fn custom_envfile_loaded_vars_state() { let testenv = init_custom_envfile_testenv(); - test_in_env(testenv, || { + test_in_env(&testenv, || { wrap::dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); assert_env_vars(CUSTOM_VARS); @@ -78,13 +78,13 @@ mod init_with_envfile { #[test] fn empty_envfile_exists() { let testenv = init_empty_envfile_testenv(); - assert_envfiles_exist_in_testenv(testenv); + assert_envfiles_in_testenv(&testenv); } #[test] fn empty_envfile_loaded_vars_state() { let testenv = init_empty_envfile_testenv(); - test_in_env(testenv, || { + test_in_env(&testenv, || { wrap::dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); }); @@ -93,13 +93,13 @@ mod init_with_envfile { #[test] fn custom_bom_envfile_exists() { let testenv = init_custom_bom_envfile_testenv(); - assert_envfiles_exist_in_testenv(testenv); + assert_envfiles_in_testenv(&testenv); } #[test] fn custom_bom_envfile_loaded_vars_state() { let testenv = init_custom_bom_envfile_testenv(); - test_in_env(testenv, || { + test_in_env(&testenv, || { wrap::dotenv().expect(DOTENV_EXPECT); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); From a1a554d0546139d5583a019c185b9793c97c7cb9 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 13:34:39 +0100 Subject: [PATCH 20/44] Reflect test harness changes in API docs --- test_util/src/lib.rs | 5 +++-- test_util/src/testenv.rs | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 4d2c78c3..1ccac2c4 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -32,10 +32,11 @@ //! let mut testenv = TestEnv::init(); //! //! // with an existing environment variable -//! testenv.set_env_var(EXISTING_KEY, EXISTING_VAL); +//! testenv.add_env_var(EXISTING_KEY, EXISTING_VAL); //! //! // with an envfile that overrides it -//! testenv.set_envfile_contents( +//! testenv.add_envfile( +//! ".env", //! create_custom_envfile(&[(EXISTING_KEY, OVERRIDING_VAL)]), //! ); //! diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index b7fb68c7..af0a0e07 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -23,7 +23,7 @@ static ENV_LOCKER: OnceCell>> = OnceCell::new(); /// /// Creation methods: /// - [`TestEnv::init`]: blank environment (no envfile) -/// - [`TestEnv::init_with_envfile`]: blank environment with an envfile +/// - [`TestEnv::init_with_envfile`]: blank environment with a custom `.env` /// - [`TestEnv::default`]: default testing environment (1 existing var and 2 /// set in a `.env` file) #[derive(Debug)] @@ -95,9 +95,8 @@ where impl TestEnv { /// Blank testing environment in a new temporary directory. /// - /// No envfile_contents or pre-existing variables to set. The envfile_name - /// is set to `.env` but won't be written until its content is set. The - /// working directory is the created temporary directory. + /// No envfile or pre-existing variables set. The working directory is the + /// created temporary directory. pub fn init() -> Self { let tempdir = tempdir().expect("create tempdir"); let work_dir = tempdir.path().to_owned(); @@ -111,7 +110,7 @@ impl TestEnv { /// Testing environment with custom envfile contents. /// - /// No pre-existing env_vars set. The envfile_name is set to `.env`. The + /// No pre-existing env_vars set. The envfile path is set to `.env`. The /// working directory is the created temporary directory. pub fn init_with_envfile(contents: impl Into>) -> Self { let mut test_env = Self::init(); From 3dfc3945482ad188094a9ff9d43c01fe23fc1125 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Mon, 15 Jul 2024 15:10:11 +0100 Subject: [PATCH 21/44] Test adding envfiles to testenv --- test_util/src/testenv.rs | 21 +++++++++++-- test_util/src/tests/testenv.rs | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index af0a0e07..45248c0a 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -119,13 +119,30 @@ impl TestEnv { } /// Add an individual envfile. + /// + /// ## Arguments + /// + /// - `path`: relative from the temporary directory + /// - `contents`: bytes or string + /// + /// ## Panics + /// + /// - if the path is empty or the same as the temporary directory + /// - if the envfile already exists pub fn add_envfile(&mut self, path: P, contents: C) -> &mut Self where - P: Into, + P: AsRef, C: Into>, { + let path = self.temp_dir.path().join(path); + if path == self.temp_path() { + panic!("path cannot be empty or the same as the temporary directory"); + } + if self.envfiles.iter().any(|f| f.path == path) { + panic!("envfile already in testenv: {}", path.display()); + } let envfile = EnvFile { - path: self.temp_dir.path().join(path.into()), + path, contents: contents.into(), }; self.envfiles.push(envfile); diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 17d7868d..fd76d125 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -127,3 +127,60 @@ mod init_with_envfile { TestEnv::init_with_envfile(envfile) } } + +mod add_envfile { + use super::*; + + #[test] + #[should_panic] + fn panics_add_twice() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".env", create_default_envfile()); + testenv.add_envfile(".env", create_custom_envfile(CUSTOM_VARS)); + } + + #[test] + #[should_panic] + fn panics_same_path_as_init() { + let mut testenv = TestEnv::init_with_envfile(create_default_envfile()); + testenv.add_envfile(".env", create_default_envfile()); + } + + #[test] + #[should_panic] + fn panics_same_path_as_default() { + let mut testenv = TestEnv::default(); + testenv.add_envfile(".env", create_invalid_envfile()); + } + + #[test] + #[should_panic] + fn panics_path() { + let mut testenv = TestEnv::init(); + testenv.add_envfile("", create_default_envfile()); + } + + #[test] + fn allow_empty_contents() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".env", []); + assert_envfiles_in_testenv(&testenv); + } + + #[test] + fn allow_absolute_path() { + let mut testenv = TestEnv::init(); + let path = testenv.temp_path().join(".env"); + assert!(path.is_absolute()); + testenv.add_envfile(&path, create_default_envfile()); + assert_envfiles_in_testenv(&testenv); + } + + #[test] + fn two_files() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".env", create_default_envfile()); + testenv.add_envfile(".env.local", create_custom_envfile(CUSTOM_VARS)); + assert_envfiles_in_testenv(&testenv); + } +} From c70aa939d12b6e52a1a1b316db33832552bc0e8f Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 16 Jul 2024 11:11:26 +0100 Subject: [PATCH 22/44] Clean TestEnv::add_envfile --- test_util/src/testenv.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 45248c0a..7b8bd79a 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -135,18 +135,8 @@ impl TestEnv { C: Into>, { let path = self.temp_dir.path().join(path); - if path == self.temp_path() { - panic!("path cannot be empty or the same as the temporary directory"); - } - if self.envfiles.iter().any(|f| f.path == path) { - panic!("envfile already in testenv: {}", path.display()); - } - let envfile = EnvFile { - path, - contents: contents.into(), - }; - self.envfiles.push(envfile); - self + self.assert_envfile_path_is_valid(&path); + self.add_envfile_assume_valid(path, contents.into()) } /// Add an individual environment variable. @@ -245,6 +235,21 @@ impl TestEnv { pub fn envfiles(&self) -> &[EnvFile] { &self.envfiles } + + fn add_envfile_assume_valid(&mut self, path: PathBuf, contents: Vec) -> &mut Self { + let envfile = EnvFile { path, contents }; + self.envfiles.push(envfile); + self + } + + fn assert_envfile_path_is_valid(&self, path: &Path) { + if path == self.temp_path() { + panic!("path cannot be empty or the same as the temporary directory"); + } + if self.envfiles.iter().any(|f| f.path == path) { + panic!("envfile already in testenv: {}", path.display()); + } + } } impl Default for TestEnv { From 9ba20ddbef9333ff0bb1f822282e456bfa8fb368 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 16 Jul 2024 13:05:59 +0100 Subject: [PATCH 23/44] Test adding env vars to testenv --- test_util/src/testenv.rs | 27 ++++++++++++++++++---- test_util/src/tests/mod.rs | 4 ++++ test_util/src/tests/testenv.rs | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 7b8bd79a..8c99b65d 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -143,16 +143,19 @@ impl TestEnv { /// /// This adds more pre-existing environment variables to the process before /// any tests are run. + /// + /// ## Panics + /// + /// - if the env var already exists in the testenv + /// - if the key is empty pub fn add_env_var(&mut self, key: K, value: V) -> &mut Self where K: Into, V: Into, { - self.env_vars.push(KeyVal { - key: key.into(), - value: value.into(), - }); - self + let key = key.into(); + self.assert_env_var_is_valid(&key); + self.add_env_var_assume_valid(key, value.into()) } /// Set all the pre-existing environment variables. @@ -242,6 +245,11 @@ impl TestEnv { self } + fn add_env_var_assume_valid(&mut self, key: String, value: String) -> &mut Self { + self.env_vars.push(KeyVal { key, value }); + self + } + fn assert_envfile_path_is_valid(&self, path: &Path) { if path == self.temp_path() { panic!("path cannot be empty or the same as the temporary directory"); @@ -250,6 +258,15 @@ impl TestEnv { panic!("envfile already in testenv: {}", path.display()); } } + + fn assert_env_var_is_valid(&self, key: &str) { + if key.is_empty() { + panic!("env_var key cannot be empty"); + } + if self.env_vars.iter().any(|kv| kv.key == key) { + panic!("env var already in testenv: {}", key); + } + } } impl Default for TestEnv { diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index dcd55885..56b809f4 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -40,3 +40,7 @@ fn assert_envfile(path: &Path, expected: &[u8]) { fn assert_default_keys_not_set_in_testenv(testenv: &TestEnv) { test_in_env(testenv, assert_default_keys_unset); } + +fn assert_env_vars_in_testenv(testenv: &TestEnv, vars: &[(&str, &str)]) { + test_in_env(testenv, || assert_env_vars(vars)); +} diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index fd76d125..9a7ffe65 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -184,3 +184,45 @@ mod add_envfile { assert_envfiles_in_testenv(&testenv); } } + +mod add_env_var { + use super::*; + + #[test] + #[should_panic] + fn panics_add_twice() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("TEST_KEY", "one_value"); + testenv.add_env_var("TEST_KEY", "two_value"); + } + + #[test] + #[should_panic] + fn panics_same_var_as_default() { + let mut testenv = TestEnv::default(); + testenv.add_env_var(DEFAULT_EXISTING_KEY, "value"); + } + + #[test] + #[should_panic] + fn panics_emtpy_key() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("", "value"); + } + + #[test] + fn allow_empty_value() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("TEST_KEY", ""); + assert_env_vars_in_testenv(&testenv, &[("TEST_KEY", "")]); + } + + #[test] + fn two_vars() { + let mut testenv = TestEnv::init(); + let vars = [("TEST_KEY", "one_value"), ("TEST_KEY_2", "two_value")]; + testenv.add_env_var(vars[0].0, vars[0].1); + testenv.add_env_var(vars[1].0, vars[1].1); + assert_env_vars_in_testenv(&testenv, &vars); + } +} From 0fce9796ab029c17bbabcac7127c4067d617c9c9 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 16 Jul 2024 14:17:24 +0100 Subject: [PATCH 24/44] Simplify adding env vars to testenv --- test_util/src/testenv.rs | 96 +++++++--------------------------- test_util/src/tests/testenv.rs | 55 +++++++++++++++++++ 2 files changed, 75 insertions(+), 76 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 8c99b65d..5ea37f50 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -30,7 +30,7 @@ static ENV_LOCKER: OnceCell>> = OnceCell::new(); pub struct TestEnv { temp_dir: TempDir, work_dir: PathBuf, - env_vars: Vec, + env_vars: EnvMap, envfiles: Vec, } @@ -41,13 +41,6 @@ pub struct EnvFile { pub contents: Vec, } -/// Simple key value struct for representing environment variables -#[derive(Debug, Clone)] -pub struct KeyVal { - pub key: String, - pub value: String, -} - /// Run a test closure within a test environment. /// /// Resets the environment variables, loads the [`TestEnv`], then runs the test @@ -155,7 +148,8 @@ impl TestEnv { { let key = key.into(); self.assert_env_var_is_valid(&key); - self.add_env_var_assume_valid(key, value.into()) + self.env_vars.insert(key, value.into()); + self } /// Set all the pre-existing environment variables. @@ -164,30 +158,14 @@ impl TestEnv { /// test is run. This overrides any previous env vars added to the /// [`TestEnv`]. /// - /// If you wish to just use a slice of tuples, use - /// [`set_env_vars_tuple`](TestEnv::set_env_vars_tuple) instead. - pub fn set_env_vars(&mut self, env_vars: Vec) -> &mut Self { - self.env_vars = env_vars; - self - } - - /// Set all the pre-existing environment variables using [`str`] tuples. - /// - /// These variables will get added to the process' environment before the - /// test is run. This overrides any previous env vars added to the - /// [`TestEnv`]. + /// ## Panics /// - /// If you wish to add an owned `Vec` instead of `str` tuples, use - /// [`set_env_vars`](TestEnv::set_env_vars) instead. - pub fn set_env_vars_tuple(&mut self, env_vars: &[(&str, &str)]) -> &mut Self { - self.env_vars = env_vars - .iter() - .map(|(key, value)| KeyVal { - key: key.to_string(), - value: value.to_string(), - }) - .collect(); - + /// - if an env var is set twice + /// - if a key is empty + pub fn set_env_vars(&mut self, env_vars: &[(&str, &str)]) -> &mut Self { + for &(key, value) in env_vars { + self.add_env_var(key, value); + } self } @@ -212,8 +190,8 @@ impl TestEnv { if let Err(err) = fs::create_dir_all(child_dir) { panic!( "unable to create child directory: `{}` in `{}`: {}", - self.temp_path().display(), path.display(), + self.temp_path().display(), err ); } @@ -230,7 +208,7 @@ impl TestEnv { } /// Reference to environment variables that will be set **before** the test. - pub fn env_vars(&self) -> &[KeyVal] { + pub fn env_vars(&self) -> &EnvMap { &self.env_vars } @@ -245,11 +223,6 @@ impl TestEnv { self } - fn add_env_var_assume_valid(&mut self, key: String, value: String) -> &mut Self { - self.env_vars.push(KeyVal { key, value }); - self - } - fn assert_envfile_path_is_valid(&self, path: &Path) { if path == self.temp_path() { panic!("path cannot be empty or the same as the temporary directory"); @@ -261,49 +234,20 @@ impl TestEnv { fn assert_env_var_is_valid(&self, key: &str) { if key.is_empty() { - panic!("env_var key cannot be empty"); + panic!("key cannot be empty"); } - if self.env_vars.iter().any(|kv| kv.key == key) { - panic!("env var already in testenv: {}", key); + if self.env_vars.contains_key(key) { + panic!("key already in testenv: {}", key); } } } impl Default for TestEnv { fn default() -> Self { - let temp_dir = tempdir().expect("create tempdir"); - let work_dir = temp_dir.path().to_owned(); - let env_vars = vec![KeyVal { - key: DEFAULT_EXISTING_KEY.into(), - value: DEFAULT_EXISTING_VALUE.into(), - }]; - let envfiles = vec![EnvFile { - path: work_dir.join(".env"), - contents: create_default_envfile().into(), - }]; - Self { - temp_dir, - work_dir, - env_vars, - envfiles, - } - } -} - -impl From<(&str, &str)> for KeyVal { - fn from(kv: (&str, &str)) -> Self { - let (key, value) = kv; - Self { - key: key.to_string(), - value: value.to_string(), - } - } -} - -impl From<(String, String)> for KeyVal { - fn from(kv: (String, String)) -> Self { - let (key, value) = kv; - Self { key, value } + let mut testenv = TestEnv::init(); + testenv.add_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); + testenv.add_envfile(".env", create_default_envfile()); + testenv } } @@ -337,7 +281,7 @@ fn create_env(test_env: &TestEnv) { create_envfile(path, contents); } - for KeyVal { key, value } in &test_env.env_vars { + for (key, value) in &test_env.env_vars { env::set_var(key, value) } } diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 9a7ffe65..e771fe89 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -225,4 +225,59 @@ mod add_env_var { testenv.add_env_var(vars[1].0, vars[1].1); assert_env_vars_in_testenv(&testenv, &vars); } + + #[test] + fn owned_strings() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("TEST_KEY".to_string(), "test_val".to_string()); + assert_env_vars_in_testenv(&testenv, &[("TEST_KEY", "test_val")]); + } +} + +mod set_env_vars { + use super::*; + + #[test] + #[should_panic] + fn panics_double_key() { + let mut testenv = TestEnv::init(); + let mut vars = VARS.to_vec(); + vars.push(VARS[0]); + testenv.set_env_vars(&vars); + } + + #[test] + #[should_panic] + fn panics_empty_key() { + let mut testenv = TestEnv::init(); + testenv.set_env_vars(&[("", "value")]); + } + + #[test] + fn from_tuples_slice() { + let mut testenv = TestEnv::init(); + testenv.set_env_vars(VARS.as_slice()); + assert_vars_in_testenv(&testenv); + } + + #[test] + fn from_tuples_ref() { + let mut testenv = TestEnv::init(); + testenv.set_env_vars(&VARS); + assert_vars_in_testenv(&testenv); + } + + #[test] + fn from_vec_slice() { + let mut testenv = TestEnv::init(); + let vec = VARS.to_vec(); + testenv.set_env_vars(vec.as_slice()); + assert_vars_in_testenv(&testenv); + } + + const VARS: [(&str, &str); 2] = [("TEST_KEY", "one_value"), ("TEST_KEY_2", "two_value")]; + + fn assert_vars_in_testenv(testenv: &TestEnv) { + assert_env_vars_in_testenv(testenv, &VARS); + } } From 69924165cf991f37dd54fb2a24bd5eb688a8e2e9 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 16 Jul 2024 15:26:00 +0100 Subject: [PATCH 25/44] Test rest of TestEnv --- test_util/src/testenv.rs | 17 +++++-- test_util/src/tests/mod.rs | 5 ++ test_util/src/tests/testenv.rs | 87 ++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 5ea37f50..7c61562a 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -173,8 +173,19 @@ impl TestEnv { /// /// The default is the created temporary directory. This method is useful if /// you wish to run a test from a subdirectory or somewhere else. - pub fn set_work_dir(&mut self, path: impl Into) -> &mut Self { - self.work_dir = path.into(); + /// + /// ## Arguments + /// + /// - `path`: relative from the temporary directory + /// + /// ## Panics + /// + /// - if the path does not exist + pub fn set_work_dir(&mut self, path: impl AsRef) -> &mut Self { + self.work_dir = self.temp_path().join(path.as_ref()); + if !self.work_dir.exists() { + panic!("work_dir does not exist: {}", self.work_dir.display()); + } self } @@ -184,7 +195,7 @@ impl TestEnv { /// the envfile is created. /// /// Will create parent directories if they are missing. - pub fn add_child_dir_all(&self, path: impl AsRef) { + pub fn add_child_dir(&self, path: impl AsRef) { let path = path.as_ref(); let child_dir = self.temp_path().join(path); if let Err(err) = fs::create_dir_all(child_dir) { diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index 56b809f4..00c56e73 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -44,3 +44,8 @@ fn assert_default_keys_not_set_in_testenv(testenv: &TestEnv) { fn assert_env_vars_in_testenv(testenv: &TestEnv, vars: &[(&str, &str)]) { test_in_env(testenv, || assert_env_vars(vars)); } + +fn assert_path_exists_in_testenv(testenv: &TestEnv, path: impl AsRef) { + let path = testenv.temp_path().join(path.as_ref()); + assert!(path.exists(), "{} should exist in testenv", path.display()); +} diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index e771fe89..b8d4839a 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -19,6 +19,24 @@ mod init { assert!(wrap::dotenv().is_err()); }); } + + #[test] + fn work_dir_is_temp() { + let testenv = TestEnv::init(); + assert_eq!(testenv.work_dir(), testenv.temp_path()); + } + + #[test] + fn env_vars_are_empty() { + let testenv = TestEnv::init(); + assert!(testenv.env_vars().is_empty()); + } + + #[test] + fn envfiles_are_empty() { + let testenv = TestEnv::init(); + assert!(testenv.envfiles().is_empty()); + } } mod init_with_envfile { @@ -281,3 +299,72 @@ mod set_env_vars { assert_env_vars_in_testenv(testenv, &VARS); } } + +mod set_work_dir { + use super::*; + + #[test] + #[should_panic] + fn panics_non_existing() { + let mut testenv = TestEnv::init(); + testenv.set_work_dir("subdir"); + } + + #[test] + fn allow_absolute_path() { + let mut testenv = TestEnv::init(); + let path = testenv.temp_path().join("subdir"); + assert!(path.is_absolute()); + std::fs::create_dir_all(&path).expect("failed to create subdir"); + testenv.set_work_dir(&path); + assert_path_exists_in_testenv(&testenv, "subdir"); + } + + #[test] + fn relative_path() { + let mut testenv = TestEnv::init(); + std::fs::create_dir_all(testenv.temp_path().join("subdir")) + .expect("failed to create subdir"); + testenv.set_work_dir("subdir"); + assert_path_exists_in_testenv(&testenv, "subdir"); + } + + #[test] + fn in_testenv() { + let mut testenv = TestEnv::init(); + std::fs::create_dir_all(testenv.temp_path().join("subdir")) + .expect("failed to create subdir"); + testenv.set_work_dir("subdir"); + test_in_env(&testenv, || { + let current_dir = std::env::current_dir().expect("failed to get current dir"); + assert_eq!(current_dir, testenv.work_dir()); + }); + } +} + +mod add_child_dir { + use super::*; + + #[test] + fn subdir() { + let testenv = TestEnv::init(); + testenv.add_child_dir("subdir"); + assert_path_exists_in_testenv(&testenv, "subdir"); + } + + #[test] + fn allow_absolute_path() { + let testenv = TestEnv::init(); + let path = testenv.temp_path().join("subdir"); + assert!(path.is_absolute()); + testenv.add_child_dir(&path); + assert_path_exists_in_testenv(&testenv, "subdir"); + } + + #[test] + fn create_parents() { + let testenv = TestEnv::init(); + testenv.add_child_dir("subdir/subsubdir"); + assert_path_exists_in_testenv(&testenv, "subdir/subsubdir"); + } +} From 55ed5013765fd047ef6e2628b514867aba863127 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 11:09:22 +0100 Subject: [PATCH 26/44] Test envfile functions --- test_util/src/envfile.rs | 2 +- test_util/src/tests/envfile.rs | 37 ++++++++++++++++++++++++++ test_util/src/tests/envfile_builder.rs | 11 -------- test_util/src/tests/mod.rs | 12 +++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 test_util/src/tests/envfile.rs diff --git a/test_util/src/envfile.rs b/test_util/src/envfile.rs index 39da4317..cbc0be65 100644 --- a/test_util/src/envfile.rs +++ b/test_util/src/envfile.rs @@ -1,11 +1,11 @@ use super::*; -#[inline(always)] /// Create the default envfile contents. /// /// [`DEFAULT_TEST_KEY`] set as [`DEFAULT_TEST_VALUE`] /// /// [`DEFAULT_EXISTING_KEY`] set as [`DEFAULT_OVERRIDING_VALUE`] +#[inline(always)] pub fn create_default_envfile() -> String { format!( "{}={}\n{}={}", diff --git a/test_util/src/tests/envfile.rs b/test_util/src/tests/envfile.rs new file mode 100644 index 00000000..45239482 --- /dev/null +++ b/test_util/src/tests/envfile.rs @@ -0,0 +1,37 @@ +use super::*; + +#[test] +fn create_default() { + let expected = format!( + "{}={}\n{}={}", + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE + ); + let actual = create_default_envfile(); + assert_eq!( + expected, actual, + "envfile should be created with default values" + ); +} + +#[test] +fn create_invalid() { + let expected = format!( + "{}{}\n{}{}", + DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE, DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE + ); + let actual = create_invalid_envfile(); + assert_eq!( + expected, actual, + "envfile should be created without equals sign" + ); +} + +#[test] +fn create_custom() { + let expected = expected_envfile(CUSTOM_VARS); + let actual = create_custom_envfile(CUSTOM_VARS); + assert_eq!( + expected, actual, + "envfile should be created with custom values" + ); +} diff --git a/test_util/src/tests/envfile_builder.rs b/test_util/src/tests/envfile_builder.rs index cd720fed..dd4ecd5f 100644 --- a/test_util/src/tests/envfile_builder.rs +++ b/test_util/src/tests/envfile_builder.rs @@ -93,14 +93,3 @@ fn assert_contents_str(efb: EnvFileBuilder, expected: &str) { let contents = efb.into_owned_string(); assert_eq!(expected, contents,); } - -fn expected_envfile(env_vars: &[(&str, &str)]) -> String { - let mut envfile = String::new(); - for (key, value) in env_vars { - envfile.push_str(key); - envfile.push('='); - envfile.push_str(value); - envfile.push('\n'); - } - envfile -} diff --git a/test_util/src/tests/mod.rs b/test_util/src/tests/mod.rs index 00c56e73..55921981 100644 --- a/test_util/src/tests/mod.rs +++ b/test_util/src/tests/mod.rs @@ -3,6 +3,7 @@ use std::path::Path; use super::*; mod default_env; +mod envfile; mod envfile_builder; mod testenv; @@ -49,3 +50,14 @@ fn assert_path_exists_in_testenv(testenv: &TestEnv, path: impl AsRef) { let path = testenv.temp_path().join(path.as_ref()); assert!(path.exists(), "{} should exist in testenv", path.display()); } + +fn expected_envfile(env_vars: &[(&str, &str)]) -> String { + let mut envfile = String::new(); + for (key, value) in env_vars { + envfile.push_str(key); + envfile.push('='); + envfile.push_str(value); + envfile.push('\n'); + } + envfile +} From 34712110d41aedb626c3ca2cbabb42f26a88bb61 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 11:45:12 +0100 Subject: [PATCH 27/44] Document test util with a readme --- test_util/LICENSE | 21 +++++++ test_util/README.md | 135 +++++++++++++++++++++++++++++++++++++++++++ test_util/src/lib.rs | 5 +- 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 test_util/LICENSE create mode 100644 test_util/README.md diff --git a/test_util/LICENSE b/test_util/LICENSE new file mode 100644 index 00000000..1fc6dfdc --- /dev/null +++ b/test_util/LICENSE @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2024 Christopher Morton and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/test_util/README.md b/test_util/README.md new file mode 100644 index 00000000..b75b3820 --- /dev/null +++ b/test_util/README.md @@ -0,0 +1,135 @@ +# dotenvy test util + +This is an internal package used for testing dotenvy. + +## Why + +Eases the annoyance of setting up custom `.env` files, managing existing +environment variables, and running multiple tests at once. + +## How + +By setting up a `TestEnv`, and running a closure via `test_in_env`. + +Before executing the closure, the `TestEnv` will: + +- Create a temporary directory +- Lock the environment from other tests +- Store the existing environment variables +- Add any custom env_vars to the environment +- Create any custom envfiles in the temporary directory + +In the closure you can: + +- Use the `wrap` module to call dotenvy's api +- Use the assertion functions to test the environment + +After executing the closure, the `TestEnv` will: + +- Remove the temporary directory +- Restore the environment variables to the original state +- Unlock the environment + +See the API docs for more details. For now, they must be built locally with +`cargo doc`. + +### Commented example + +```rust +use dotenvy_test_util::*; + +const EXISTING_KEY: &str = "TEST_KEY"; +const EXISTING_VAL: &str = "test_val"; +const OVERRIDING_VAL: &str = "overriding_val"; + +#[test] +fn dotenv_override_existing_key() { + // setup testing environment + let mut testenv = TestEnv::init(); + + // with an existing environment variable + testenv.add_env_var(EXISTING_KEY, EXISTING_VAL); + + // with an envfile that overrides it + testenv.add_envfile( + ".env", + create_custom_envfile(&[(EXISTING_KEY, OVERRIDING_VAL)]), + ); + + // execute a closure in the testing environment + test_in_env(&testenv, || { + wrap::dotenv_override().expect(".env should be loaded"); + assert_env_var(EXISTING_KEY, OVERRIDING_VAL); + }); + // any changes to environment variables will be reset for other tests +} +``` + +### Default TestEnv + +Use the default `TestEnv` for simple tests. + +```rust +use dotenvy_test_util::*; + +#[test] +fn dotenv_works() { + test_in_default_env(|| { + wrap::dotenv().expect(".env should be loaded"); + assert_env_var(DEFAULT_KEY, DEFAULT_VAL); + }) +} +``` + +The default `TestEnv` has 1 existing environment variable: + +- `DEFAULT_EXISTING_KEY` set to `DEFAULT_EXISTING_VAL` + +It has an envfile `.env` that sets: + +- `DEFAULT_TEST_KEY` to `DEFAULT_TEST_VAL` +- `DEFAULT_EXISTING_KEY` to `DEFAULT_OVERRIDING_VAL` + +### Customised Envfile + +Use the `EnvFileBuilder` to manipulate the content of an envfile. Useful +for byte-order-mark(BOM) testing, and other edge cases. + +```rust +use dotenvy_test_util::*; + +#[test] +fn comments_ignored_in_utf8bom_envfile() { + let mut efb = EnvFileBuilder::new(); + efb.insert_utf8_bom(); + efb.add_strln("# TEST_KEY=TEST_VAL"); + let envfile = efb.into_owned_bytes(); + + let testenv = TestEnv::init_with_envfile(envfile); + + test_in_env(&testenv, || { + wrap::dotenv().expect(".env should be loaded"); + assert_env_var_unset("TEST_KEY"); + }); +} +``` + +Or use anything that can be converted to a `Vec` if your envfile is +simple. + +```rust +use dotenvy_test_util::*; + +#[test] +fn comments_ignored() { + let envfile = "# TEST_KEY=TEST_VAL\n"; + + let testenv = TestEnv::init_with_envfile(envfile); + + test_in_env(&testenv, || { + wrap::dotenv().expect(".env should be loaded"); + assert_env_var_unset("TEST_KEY"); + }); +} +``` + diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 1ccac2c4..bc6f0625 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -6,6 +6,7 @@ //! - pre-existing environment variables, //! - different directory layouts, //! - custom `.env` file contents, +//! - multiple `.env` files, //! - custom envfile name/path. //! //! Use the `create_` helper functions, such as [`create_custom_envfile`], to @@ -40,8 +41,8 @@ //! create_custom_envfile(&[(EXISTING_KEY, OVERRIDING_VAL)]), //! ); //! -//! // execute the closure in the testing environment -//! test_in_env(testenv, || { +//! // execute a closure in the testing environment +//! test_in_env(&testenv, || { //! wrap::dotenv_override().expect(".env should be loaded"); //! assert_env_var(EXISTING_KEY, OVERRIDING_VAL); //! }); From 6edeb9fc6d2f068718d4198857152df19bb27476 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 13:50:12 +0100 Subject: [PATCH 28/44] Fix macos working directory --- test_util/src/testenv.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 7c61562a..a8553593 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -182,7 +182,11 @@ impl TestEnv { /// /// - if the path does not exist pub fn set_work_dir(&mut self, path: impl AsRef) -> &mut Self { - self.work_dir = self.temp_path().join(path.as_ref()); + self.work_dir = self + .temp_path() + .join(path.as_ref()) + .canonicalize() + .expect("canonicalize work_dir"); if !self.work_dir.exists() { panic!("work_dir does not exist: {}", self.work_dir.display()); } From 896c32f3ebfc8d28f60a53c35a9dd1df1b55137c Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 15:54:09 +0100 Subject: [PATCH 29/44] Remove wrap module --- test_util/README.md | 21 ++++---- test_util/src/lib.rs | 11 ++-- test_util/src/tests/default_env.rs | 2 +- test_util/src/tests/testenv.rs | 11 ++-- test_util/src/wrap.rs | 87 ------------------------------ 5 files changed, 23 insertions(+), 109 deletions(-) delete mode 100644 test_util/src/wrap.rs diff --git a/test_util/README.md b/test_util/README.md index b75b3820..fe59c622 100644 --- a/test_util/README.md +++ b/test_util/README.md @@ -11,7 +11,7 @@ environment variables, and running multiple tests at once. By setting up a `TestEnv`, and running a closure via `test_in_env`. -Before executing the closure, the `TestEnv` will: +**Before** executing the closure, the `TestEnv` will: - Create a temporary directory - Lock the environment from other tests @@ -19,12 +19,9 @@ Before executing the closure, the `TestEnv` will: - Add any custom env_vars to the environment - Create any custom envfiles in the temporary directory -In the closure you can: +**In the closure** you can use the assertion functions to test the environment. -- Use the `wrap` module to call dotenvy's api -- Use the assertion functions to test the environment - -After executing the closure, the `TestEnv` will: +**After** executing the closure, the `TestEnv` will: - Remove the temporary directory - Restore the environment variables to the original state @@ -37,6 +34,7 @@ See the API docs for more details. For now, they must be built locally with ```rust use dotenvy_test_util::*; +use dotenvy::dotenv_override; const EXISTING_KEY: &str = "TEST_KEY"; const EXISTING_VAL: &str = "test_val"; @@ -58,7 +56,7 @@ fn dotenv_override_existing_key() { // execute a closure in the testing environment test_in_env(&testenv, || { - wrap::dotenv_override().expect(".env should be loaded"); + dotenv_override().expect(".env should be loaded"); assert_env_var(EXISTING_KEY, OVERRIDING_VAL); }); // any changes to environment variables will be reset for other tests @@ -71,11 +69,12 @@ Use the default `TestEnv` for simple tests. ```rust use dotenvy_test_util::*; +use dotenvy::dotenv; #[test] fn dotenv_works() { test_in_default_env(|| { - wrap::dotenv().expect(".env should be loaded"); + dotenv().expect(".env should be loaded"); assert_env_var(DEFAULT_KEY, DEFAULT_VAL); }) } @@ -97,6 +96,7 @@ for byte-order-mark(BOM) testing, and other edge cases. ```rust use dotenvy_test_util::*; +use dotenvy::dotenv; #[test] fn comments_ignored_in_utf8bom_envfile() { @@ -108,7 +108,7 @@ fn comments_ignored_in_utf8bom_envfile() { let testenv = TestEnv::init_with_envfile(envfile); test_in_env(&testenv, || { - wrap::dotenv().expect(".env should be loaded"); + dotenv().expect(".env should be loaded"); assert_env_var_unset("TEST_KEY"); }); } @@ -119,6 +119,7 @@ simple. ```rust use dotenvy_test_util::*; +use dotenvy::dotenv; #[test] fn comments_ignored() { @@ -127,7 +128,7 @@ fn comments_ignored() { let testenv = TestEnv::init_with_envfile(envfile); test_in_env(&testenv, || { - wrap::dotenv().expect(".env should be loaded"); + dotenv().expect(".env should be loaded"); assert_env_var_unset("TEST_KEY"); }); } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index bc6f0625..ea775d0b 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -13,15 +13,15 @@ //! generate the `.env` file contents. If you need more control of the //! envfile's bytes, use the [`EnvFileBuilder`]. //! -//! In your tests, call the [`dotenvy`] API via the [`wrap`] module to keep any -//! API changes in one place. Then make use of the `assert_` helpers, such as -//! [`assert_env_var`] and [`assert_env_var_unset`], to check the state of the -//! environment. +//! In your tests, call the [`dotenvy`] API, then make use of the `assert_` +//! helpers, such as [`assert_env_var`] and [`assert_env_var_unset`], to check +//! the state of the environment. //! //! ## Example //! //! ```no_run //! use dotenvy_test_util::*; +//! use dotenvy::dotenv_override; //! //! const EXISTING_KEY: &str = "TEST_KEY"; //! const EXISTING_VAL: &str = "test_val"; @@ -43,7 +43,7 @@ //! //! // execute a closure in the testing environment //! test_in_env(&testenv, || { -//! wrap::dotenv_override().expect(".env should be loaded"); +//! dotenv_override().expect(".env should be loaded"); //! assert_env_var(EXISTING_KEY, OVERRIDING_VAL); //! }); //! // any changes to environment variables will be reset for other tests @@ -53,7 +53,6 @@ mod assertions; mod envfile; mod testenv; -pub mod wrap; #[cfg(test)] mod tests; diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index f9d5b09b..68d56b5a 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -17,7 +17,7 @@ fn envfile_exists() { #[test] fn envfile_loaded_vars_state() { test_in_default_env(|| { - wrap::dotenv().expect(DOTENV_EXPECT); + dotenvy::dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index b8d4839a..26290153 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -1,4 +1,5 @@ use super::*; +use dotenvy::dotenv; mod init { use super::*; @@ -16,7 +17,7 @@ mod init { test_in_env(&init_testenv, || { assert!(!envfile_path.exists()); - assert!(wrap::dotenv().is_err()); + assert!(dotenv().is_err()); }); } @@ -58,7 +59,7 @@ mod init_with_envfile { fn default_envfile_loaded_vars_state() { let testenv = init_default_envfile_testenv(); test_in_env(&testenv, || { - wrap::dotenv().expect(DOTENV_EXPECT); + dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var // but existing key is not set in this testenv assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE); @@ -87,7 +88,7 @@ mod init_with_envfile { fn custom_envfile_loaded_vars_state() { let testenv = init_custom_envfile_testenv(); test_in_env(&testenv, || { - wrap::dotenv().expect(DOTENV_EXPECT); + dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); assert_env_vars(CUSTOM_VARS); }); @@ -103,7 +104,7 @@ mod init_with_envfile { fn empty_envfile_loaded_vars_state() { let testenv = init_empty_envfile_testenv(); test_in_env(&testenv, || { - wrap::dotenv().expect(DOTENV_EXPECT); + dotenv().expect(DOTENV_EXPECT); assert_default_keys_unset(); }); } @@ -118,7 +119,7 @@ mod init_with_envfile { fn custom_bom_envfile_loaded_vars_state() { let testenv = init_custom_bom_envfile_testenv(); test_in_env(&testenv, || { - wrap::dotenv().expect(DOTENV_EXPECT); + dotenv().expect(DOTENV_EXPECT); assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); }); } diff --git a/test_util/src/wrap.rs b/test_util/src/wrap.rs deleted file mode 100644 index 04d5a03f..00000000 --- a/test_util/src/wrap.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Wrappers for the `dotenvy` API. -//! -//! If the `dotenvy` API changes, only this module needs to be updated. -//! -//! ## Example -//! -//! ```no_run -//! use dotenvy_test_util::wrap; -//! wrap::dotenv().expect("Failed to load .env file"); -//! ``` - -use dotenvy::{self, Iter, Result}; -use std::env::Vars; -use std::ffi::OsStr; -use std::fs::File; -use std::io; -use std::path::{Path, PathBuf}; - -#[inline(always)] -pub fn var>(key: K) -> Result { - dotenvy::var(key) -} - -#[inline(always)] -pub fn vars() -> Vars { - dotenvy::vars() -} - -#[inline(always)] -pub fn from_path>(path: P) -> Result<()> { - dotenvy::from_path(path) -} - -#[inline(always)] -pub fn from_path_override>(path: P) -> Result<()> { - dotenvy::from_path_override(path) -} - -#[inline(always)] -pub fn from_path_iter>(path: P) -> Result> { - dotenvy::from_path_iter(path) -} - -#[inline(always)] -pub fn from_filename>(filename: P) -> Result { - dotenvy::from_filename(filename) -} - -#[inline(always)] -pub fn from_filename_override>(filename: P) -> Result { - dotenvy::from_filename_override(filename) -} - -#[inline(always)] -pub fn from_filename_iter>(filename: P) -> Result> { - dotenvy::from_filename_iter(filename) -} - -#[inline(always)] -pub fn from_read(reader: R) -> Result<()> { - dotenvy::from_read(reader) -} - -#[inline(always)] -pub fn from_read_override(reader: R) -> Result<()> { - dotenvy::from_read_override(reader) -} - -#[inline(always)] -pub fn from_read_iter(reader: R) -> Iter { - dotenvy::from_read_iter(reader) -} - -#[inline(always)] -pub fn dotenv() -> Result { - dotenvy::dotenv() -} - -#[inline(always)] -pub fn dotenv_override() -> Result { - dotenvy::dotenv_override() -} - -#[inline(always)] -pub fn dotenv_iter() -> Result> { - dotenvy::dotenv_iter() -} From dc14ec5478a78d74f4ed314b84db32277e21fda5 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 16:07:51 +0100 Subject: [PATCH 30/44] Add extra assertions --- test_util/src/assertions.rs | 12 ++++++++++++ test_util/src/tests/default_env.rs | 5 ++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/test_util/src/assertions.rs b/test_util/src/assertions.rs index f9fa9e68..8a169a62 100644 --- a/test_util/src/assertions.rs +++ b/test_util/src/assertions.rs @@ -61,3 +61,15 @@ pub fn assert_default_keys_unset() { assert_env_var_unset(DEFAULT_EXISTING_KEY); assert_env_var_unset(DEFAULT_TEST_KEY); } + +/// Assert default testing environment variables are set. +/// Assuming the default envfile is loaded. +pub fn assert_default_keys() { + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); + assert_default_existing_var(); +} + +/// Assert default existing environment variable is set. +pub fn assert_default_existing_var() { + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); +} diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index 68d56b5a..4ac848e0 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -3,8 +3,8 @@ use super::*; #[test] fn vars_state() { test_in_default_env(|| { - assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); assert_env_var_unset(DEFAULT_TEST_KEY); + assert_default_existing_var(); }); } @@ -19,7 +19,6 @@ fn envfile_loaded_vars_state() { test_in_default_env(|| { dotenvy::dotenv().expect(DOTENV_EXPECT); // dotenv() does not override existing var - assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); - assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); + assert_default_keys(); }); } From 1bd15d013bf2dd0aaab6aebff72298c85391f401 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 16:10:34 +0100 Subject: [PATCH 31/44] Fix naming consistency --- test_util/src/testenv.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index a8553593..5ae01846 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -45,7 +45,7 @@ pub struct EnvFile { /// /// Resets the environment variables, loads the [`TestEnv`], then runs the test /// closure. Ensures only one thread has access to the process environment. -pub fn test_in_env(test_env: &TestEnv, test: F) +pub fn test_in_env(testenv: &TestEnv, test: F) where F: FnOnce(), { @@ -55,7 +55,7 @@ where let original_env = locker.lock().unwrap_or_else(PoisonError::into_inner); // we reset the environment anyway upon acquiring the lock reset_env(&original_env); - create_env(test_env); + create_env(testenv); test(); // drop the lock } @@ -81,8 +81,8 @@ pub fn test_in_default_env(test: F) where F: FnOnce(), { - let test_env = TestEnv::default(); - test_in_env(&test_env, test); + let testenv = TestEnv::default(); + test_in_env(&testenv, test); } impl TestEnv { @@ -106,9 +106,9 @@ impl TestEnv { /// No pre-existing env_vars set. The envfile path is set to `.env`. The /// working directory is the created temporary directory. pub fn init_with_envfile(contents: impl Into>) -> Self { - let mut test_env = Self::init(); - test_env.add_envfile(".env", contents); - test_env + let mut testenv = Self::init(); + testenv.add_envfile(".env", contents); + testenv } /// Add an individual envfile. @@ -289,14 +289,14 @@ fn reset_env(original_env: &EnvMap) { /// Create an environment to run tests in. /// /// Writes the envfiles, sets the working directory, and sets environment vars. -fn create_env(test_env: &TestEnv) { - env::set_current_dir(&test_env.work_dir).expect("setting working directory"); +fn create_env(testenv: &TestEnv) { + env::set_current_dir(&testenv.work_dir).expect("setting working directory"); - for EnvFile { path, contents } in &test_env.envfiles { + for EnvFile { path, contents } in &testenv.envfiles { create_envfile(path, contents); } - for (key, value) in &test_env.env_vars { + for (key, value) in &testenv.env_vars { env::set_var(key, value) } } From 9974e0b77550032a9a26b61723e9c658bd5ebe8b Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 17:16:48 +0100 Subject: [PATCH 32/44] Add EnvFileBuilder conversions --- test_util/README.md | 3 +-- test_util/src/envfile.rs | 20 ++++++++++++++++++++ test_util/src/tests/envfile_builder.rs | 22 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/test_util/README.md b/test_util/README.md index fe59c622..f3dad08e 100644 --- a/test_util/README.md +++ b/test_util/README.md @@ -103,9 +103,8 @@ fn comments_ignored_in_utf8bom_envfile() { let mut efb = EnvFileBuilder::new(); efb.insert_utf8_bom(); efb.add_strln("# TEST_KEY=TEST_VAL"); - let envfile = efb.into_owned_bytes(); - let testenv = TestEnv::init_with_envfile(envfile); + let testenv = TestEnv::init_with_envfile(efb); test_in_env(&testenv, || { dotenv().expect(".env should be loaded"); diff --git a/test_util/src/envfile.rs b/test_util/src/envfile.rs index cbc0be65..4d09d6ab 100644 --- a/test_util/src/envfile.rs +++ b/test_util/src/envfile.rs @@ -138,3 +138,23 @@ impl EnvFileBuilder { self } } + +impl From for Vec { + fn from(builder: EnvFileBuilder) -> Self { + builder.into_owned_bytes() + } +} + +impl From> for EnvFileBuilder { + fn from(contents: Vec) -> Self { + Self { contents } + } +} + +impl From for EnvFileBuilder { + fn from(contents: String) -> Self { + Self { + contents: contents.into_bytes(), + } + } +} diff --git a/test_util/src/tests/envfile_builder.rs b/test_util/src/tests/envfile_builder.rs index dd4ecd5f..cb3dc84a 100644 --- a/test_util/src/tests/envfile_builder.rs +++ b/test_util/src/tests/envfile_builder.rs @@ -84,6 +84,28 @@ fn add_strln() { assert_contents_str(efb, "test\n"); } +#[test] +fn from_vec_u8() { + let vec: Vec = Vec::from(create_default_envfile()); + let efb = EnvFileBuilder::from(vec); + assert_contents_str(efb, &create_default_envfile()); +} + +#[test] +fn to_vec_u8() { + let mut efb = EnvFileBuilder::new(); + efb.add_str(create_default_envfile().as_str()); + let vec = Vec::from(efb); + let expected = create_default_envfile().into_bytes(); + assert_eq!(expected, vec); +} + +#[test] +fn from_string() { + let efb = EnvFileBuilder::from(create_default_envfile()); + assert_contents_str(efb, &create_default_envfile()); +} + fn assert_contents_empty(efb: EnvFileBuilder) { let contents = efb.into_owned_bytes(); assert!(contents.is_empty()); From 2d13009fa7dd2da1cd93543570928c8de6e77e52 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 15:57:21 +0100 Subject: [PATCH 33/44] Fix canonicalize paths --- test_util/src/testenv.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 5ae01846..91c9e1c2 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -28,7 +28,9 @@ static ENV_LOCKER: OnceCell>> = OnceCell::new(); /// set in a `.env` file) #[derive(Debug)] pub struct TestEnv { - temp_dir: TempDir, + // Temporary directory that will be deleted on drop + _temp_dir: TempDir, + dir_path: PathBuf, work_dir: PathBuf, env_vars: EnvMap, envfiles: Vec, @@ -92,10 +94,14 @@ impl TestEnv { /// created temporary directory. pub fn init() -> Self { let tempdir = tempdir().expect("create tempdir"); - let work_dir = tempdir.path().to_owned(); + let dir_path = tempdir + .path() + .canonicalize() + .expect("canonicalize dir_path"); Self { - temp_dir: tempdir, - work_dir, + _temp_dir: tempdir, + work_dir: dir_path.clone(), + dir_path, env_vars: Default::default(), envfiles: vec![], } @@ -127,7 +133,7 @@ impl TestEnv { P: AsRef, C: Into>, { - let path = self.temp_dir.path().join(path); + let path = self.dir_path.join(path); self.assert_envfile_path_is_valid(&path); self.add_envfile_assume_valid(path, contents.into()) } @@ -214,7 +220,7 @@ impl TestEnv { /// Reference to the path of the temporary directory. pub fn temp_path(&self) -> &Path { - self.temp_dir.path() + &self.dir_path } /// Reference to the working directory the test will be run from. From 9219e808fc3aed9e50e4ca08baf86f5a972fad97 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 23 Jul 2024 10:59:32 +0100 Subject: [PATCH 34/44] Fix method signature consistancy --- test_util/src/testenv.rs | 3 ++- test_util/src/tests/testenv.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 91c9e1c2..706e888f 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -205,7 +205,7 @@ impl TestEnv { /// the envfile is created. /// /// Will create parent directories if they are missing. - pub fn add_child_dir(&self, path: impl AsRef) { + pub fn add_child_dir(&mut self, path: impl AsRef) -> &mut Self { let path = path.as_ref(); let child_dir = self.temp_path().join(path); if let Err(err) = fs::create_dir_all(child_dir) { @@ -216,6 +216,7 @@ impl TestEnv { err ); } + self } /// Reference to the path of the temporary directory. diff --git a/test_util/src/tests/testenv.rs b/test_util/src/tests/testenv.rs index 26290153..cc4e3cd8 100644 --- a/test_util/src/tests/testenv.rs +++ b/test_util/src/tests/testenv.rs @@ -348,14 +348,14 @@ mod add_child_dir { #[test] fn subdir() { - let testenv = TestEnv::init(); + let mut testenv = TestEnv::init(); testenv.add_child_dir("subdir"); assert_path_exists_in_testenv(&testenv, "subdir"); } #[test] fn allow_absolute_path() { - let testenv = TestEnv::init(); + let mut testenv = TestEnv::init(); let path = testenv.temp_path().join("subdir"); assert!(path.is_absolute()); testenv.add_child_dir(&path); @@ -364,7 +364,7 @@ mod add_child_dir { #[test] fn create_parents() { - let testenv = TestEnv::init(); + let mut testenv = TestEnv::init(); testenv.add_child_dir("subdir/subsubdir"); assert_path_exists_in_testenv(&testenv, "subdir/subsubdir"); } From 59b46840ca4f1804306bca8e09b380f6dda0f66a Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 23 Jul 2024 11:31:17 +0100 Subject: [PATCH 35/44] Add default existing var testenv --- test_util/src/testenv.rs | 8 ++++++++ test_util/src/tests/default_env.rs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/test_util/src/testenv.rs b/test_util/src/testenv.rs index 706e888f..c51ddd20 100644 --- a/test_util/src/testenv.rs +++ b/test_util/src/testenv.rs @@ -87,6 +87,14 @@ where test_in_env(&testenv, test); } +/// Create a [`TestEnv`] without an envfile, but with the +/// default existing environment variable. +pub fn create_testenv_with_default_var() -> TestEnv { + let mut testenv = TestEnv::init(); + testenv.add_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); + testenv +} + impl TestEnv { /// Blank testing environment in a new temporary directory. /// diff --git a/test_util/src/tests/default_env.rs b/test_util/src/tests/default_env.rs index 4ac848e0..c2de3f89 100644 --- a/test_util/src/tests/default_env.rs +++ b/test_util/src/tests/default_env.rs @@ -22,3 +22,13 @@ fn envfile_loaded_vars_state() { assert_default_keys(); }); } + +#[test] +fn only_default_existing() { + let testenv = create_testenv_with_default_var(); + let envfile_path = testenv.temp_path().join(".env"); + test_in_env(&testenv, || { + assert_default_existing_var(); + assert!(!envfile_path.exists()); + }); +} From 0bb3f373cb44b4c5395151808929153f9e3223d9 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 17 Jul 2024 17:29:52 +0100 Subject: [PATCH 36/44] Test dotenv with new harness --- dotenv/tests/integration/api/dotenv.rs | 36 +++ dotenv/tests/integration/api/dotenv_iter.rs | 1 + .../tests/integration/api/dotenv_override.rs | 1 + dotenv/tests/integration/api/from_filename.rs | 1 + .../integration/api/from_filename_iter.rs | 1 + .../integration/api/from_filename_override.rs | 1 + dotenv/tests/integration/api/from_path.rs | 1 + .../tests/integration/api/from_path_iter.rs | 1 + .../integration/api/from_path_override.rs | 1 + dotenv/tests/integration/api/from_read.rs | 1 + .../tests/integration/api/from_read_iter.rs | 1 + .../integration/api/from_read_override.rs | 1 + dotenv/tests/integration/api/var.rs | 1 + dotenv/tests/integration/api/vars.rs | 1 + dotenv/tests/integration/case/bom.rs | 54 ++++ dotenv/tests/integration/case/comment.rs | 56 ++++ dotenv/tests/integration/case/directory.rs | 44 ++++ dotenv/tests/integration/case/envfile.rs | 73 +++++ dotenv/tests/integration/case/export.rs | 20 ++ dotenv/tests/integration/case/multiline.rs | 62 +++++ .../integration/case/multiline_comment.rs | 64 +++++ dotenv/tests/integration/case/quote.rs | 89 +++++++ .../integration/case/var_substitution.rs | 249 ++++++++++++++++++ dotenv/tests/integration/case/whitespace.rs | 64 +++++ dotenv/tests/integration/dotenv.rs | 10 - dotenv/tests/integration/main.rs | 35 ++- dotenv/tests/integration/util/assert.rs | 37 +++ dotenv/tests/integration/util/create.rs | 0 dotenv/tests/integration/util/mod.rs | 35 +++ dotenv/tests/integration/util/test.rs | 75 ++++++ 30 files changed, 1005 insertions(+), 11 deletions(-) create mode 100644 dotenv/tests/integration/api/dotenv.rs create mode 100644 dotenv/tests/integration/api/dotenv_iter.rs create mode 100644 dotenv/tests/integration/api/dotenv_override.rs create mode 100644 dotenv/tests/integration/api/from_filename.rs create mode 100644 dotenv/tests/integration/api/from_filename_iter.rs create mode 100644 dotenv/tests/integration/api/from_filename_override.rs create mode 100644 dotenv/tests/integration/api/from_path.rs create mode 100644 dotenv/tests/integration/api/from_path_iter.rs create mode 100644 dotenv/tests/integration/api/from_path_override.rs create mode 100644 dotenv/tests/integration/api/from_read.rs create mode 100644 dotenv/tests/integration/api/from_read_iter.rs create mode 100644 dotenv/tests/integration/api/from_read_override.rs create mode 100644 dotenv/tests/integration/api/var.rs create mode 100644 dotenv/tests/integration/api/vars.rs create mode 100644 dotenv/tests/integration/case/bom.rs create mode 100644 dotenv/tests/integration/case/comment.rs create mode 100644 dotenv/tests/integration/case/directory.rs create mode 100644 dotenv/tests/integration/case/envfile.rs create mode 100644 dotenv/tests/integration/case/export.rs create mode 100644 dotenv/tests/integration/case/multiline.rs create mode 100644 dotenv/tests/integration/case/multiline_comment.rs create mode 100644 dotenv/tests/integration/case/quote.rs create mode 100644 dotenv/tests/integration/case/var_substitution.rs create mode 100644 dotenv/tests/integration/case/whitespace.rs delete mode 100644 dotenv/tests/integration/dotenv.rs create mode 100644 dotenv/tests/integration/util/assert.rs create mode 100644 dotenv/tests/integration/util/create.rs create mode 100644 dotenv/tests/integration/util/mod.rs create mode 100644 dotenv/tests/integration/util/test.rs diff --git a/dotenv/tests/integration/api/dotenv.rs b/dotenv/tests/integration/api/dotenv.rs new file mode 100644 index 00000000..34966cdb --- /dev/null +++ b/dotenv/tests/integration/api/dotenv.rs @@ -0,0 +1,36 @@ +use crate::util::*; +use dotenvy::dotenv; +use dotenvy_test_util::*; + +#[test] +fn default_env_ok() { + test_in_default_env(|| { + dotenv().ok(); + assert_default_keys(); + }); +} + +#[test] +fn default_env_unwrap() { + test_in_default_env(|| { + dotenv().unwrap(); + assert_default_keys(); + }); +} + +#[test] +fn default_env_unwrap_path() { + let testenv = TestEnv::default(); + test_default_envfile_path(&testenv); +} + +#[test] +fn explicit_no_override() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("FOOO", "bar"); + testenv.add_envfile(".env", "FOOO=notbar"); + test_in_env(&testenv, || { + dotenv().unwrap(); + assert_env_var("FOOO", "bar"); + }) +} diff --git a/dotenv/tests/integration/api/dotenv_iter.rs b/dotenv/tests/integration/api/dotenv_iter.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/dotenv_iter.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/dotenv_override.rs b/dotenv/tests/integration/api/dotenv_override.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/dotenv_override.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_filename.rs b/dotenv/tests/integration/api/from_filename.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_filename.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_filename_iter.rs b/dotenv/tests/integration/api/from_filename_iter.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_filename_iter.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_filename_override.rs b/dotenv/tests/integration/api/from_filename_override.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_filename_override.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_path.rs b/dotenv/tests/integration/api/from_path.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_path.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_path_iter.rs b/dotenv/tests/integration/api/from_path_iter.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_path_iter.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_path_override.rs b/dotenv/tests/integration/api/from_path_override.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_path_override.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_read.rs b/dotenv/tests/integration/api/from_read.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_read.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_read_iter.rs b/dotenv/tests/integration/api/from_read_iter.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_read_iter.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/from_read_override.rs b/dotenv/tests/integration/api/from_read_override.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/from_read_override.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/var.rs b/dotenv/tests/integration/api/var.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/var.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/api/vars.rs b/dotenv/tests/integration/api/vars.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dotenv/tests/integration/api/vars.rs @@ -0,0 +1 @@ + diff --git a/dotenv/tests/integration/case/bom.rs b/dotenv/tests/integration/case/bom.rs new file mode 100644 index 00000000..ee233061 --- /dev/null +++ b/dotenv/tests/integration/case/bom.rs @@ -0,0 +1,54 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn utf8_no_vars() { + let mut efb = EnvFileBuilder::new(); + efb.insert_utf8_bom(); + let testenv = TestEnv::init_with_envfile(efb); + test_keys_unset(&testenv); +} + +#[test] +fn utf8_one_var() { + let mut efb = EnvFileBuilder::new(); + efb.insert_utf8_bom(); + efb.add_key_value(KEY_1, VAL_1); + let testenv = TestEnv::init_with_envfile(efb); + test_key_1_only(&testenv); +} + +#[test] +fn utf8_two_vars() { + let mut efb = EnvFileBuilder::new(); + efb.insert_utf8_bom(); + let vars = [(KEY_1, VAL_1), (KEY_2, VAL_2)]; + efb.add_vars(&vars); + let testenv = TestEnv::init_with_envfile(efb); + test_env_vars(&testenv, &vars); +} + +#[test] +fn invalid_no_vars() { + let mut efb = EnvFileBuilder::new(); + efb.add_bytes(b"\xFA\xFA"); + let testenv = TestEnv::init_with_envfile(efb); + test_invalid_utf8(&testenv); +} + +#[test] +fn invalid_one_var() { + let mut efb = EnvFileBuilder::new(); + efb.add_bytes(b"\xFE\xFF"); + efb.add_key_value(KEY_1, VAL_1); + let testenv = TestEnv::init_with_envfile(efb); + test_invalid_utf8(&testenv); +} + +#[test] +fn utf16_no_vars() { + let mut efb = EnvFileBuilder::new(); + efb.add_bytes(b"\xFE\xFF"); + let testenv = TestEnv::init_with_envfile(efb); + test_invalid_utf8(&testenv); +} diff --git a/dotenv/tests/integration/case/comment.rs b/dotenv/tests/integration/case/comment.rs new file mode 100644 index 00000000..a04714b4 --- /dev/null +++ b/dotenv/tests/integration/case/comment.rs @@ -0,0 +1,56 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn one() { + let testenv = TestEnv::init_with_envfile(format!("# {KEYVAL_1}")); + test_keys_unset(&testenv); +} + +#[test] +fn one_whitespace() { + let testenv = TestEnv::init_with_envfile(format!(" # {KEYVAL_1}")); + test_keys_unset(&testenv); +} + +#[test] +fn one_and_one_assign() { + let testenv = TestEnv::init_with_envfile(format!("# {KEYVAL_1}\n{KEYVAL_2}")); + test_key_2_only(&testenv); +} + +#[test] +fn one_and_one_assign_whitespace() { + let testenv = TestEnv::init_with_envfile(format!(" # {KEYVAL_1}\n{KEYVAL_2}")); + test_key_2_only(&testenv); +} + +#[test] +fn assign_same_line() { + let testenv = TestEnv::init_with_envfile(format!("{KEYVAL_1} # {KEYVAL_2}")); + test_key_1_only(&testenv); +} + +#[test] +fn hash_in_value() { + let testenv = TestEnv::init_with_envfile(format!("{KEYVAL_1}#{KEYVAL_2}")); + test_key_1_with_hash_val(&testenv); +} + +#[test] +fn hash_in_value_single_quoted() { + let testenv = TestEnv::init_with_envfile(format!("{KEY_1}='{VAL_1}'#{KEYVAL_2}")); + test_key_1_with_hash_val(&testenv); +} + +#[test] +fn hash_in_value_double_quoted() { + let testenv = TestEnv::init_with_envfile(format!(r##"{KEY_1}="{VAL_1}"#{KEYVAL_2}"##)); + test_key_1_with_hash_val(&testenv); +} + +#[test] +fn hash_in_key() { + let testenv = TestEnv::init_with_envfile("FOO#1=bar"); + test_err_line_parse(&testenv, "FOO#1=bar", 3); +} diff --git a/dotenv/tests/integration/case/directory.rs b/dotenv/tests/integration/case/directory.rs new file mode 100644 index 00000000..76ffdeb6 --- /dev/null +++ b/dotenv/tests/integration/case/directory.rs @@ -0,0 +1,44 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn child_dir() { + let mut testenv = TestEnv::init_with_envfile(KEYVAL_1); + testenv.add_child_dir("child"); + testenv.set_work_dir("child"); + test_key_1_only(&testenv); +} + +#[test] +fn child_dir_no_envfile() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.set_work_dir("child"); + test_err_not_found(&testenv); +} + +#[test] +fn parent_dir_not_found() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("child/.env", KEYVAL_1); + test_err_not_found(&testenv); +} + +#[test] +fn sibling_dir_not_found() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("brother"); + testenv.add_child_dir("sister"); + testenv.add_envfile("brother/.env", KEYVAL_1); + testenv.set_work_dir("sister"); + test_err_not_found(&testenv); +} + +#[test] +fn grandchild_dir() { + let mut testenv = TestEnv::init_with_envfile(KEYVAL_1); + testenv.add_child_dir("child/grandchild"); + testenv.set_work_dir("child/grandchild"); + test_key_1_only(&testenv); +} diff --git a/dotenv/tests/integration/case/envfile.rs b/dotenv/tests/integration/case/envfile.rs new file mode 100644 index 00000000..bfb18ac9 --- /dev/null +++ b/dotenv/tests/integration/case/envfile.rs @@ -0,0 +1,73 @@ +use crate::util::*; +use dotenvy_test_util::*; + +const ONE_WORD: &str = "oneword"; + +#[test] +#[should_panic] +fn none() { + let testenv = TestEnv::init(); + test_in_env(&testenv, api_fn); +} + +#[test] +fn none_err() { + let testenv = TestEnv::init(); + test_err_not_found(&testenv); +} + +#[test] +fn empty() { + let testenv = create_empty_envfile_testenv(); + test_in_env(&testenv, || { + api_fn(); + assert_default_existing_var(); + }); +} + +#[test] +fn empty_path() { + let testenv = create_empty_envfile_testenv(); + test_default_envfile_path(&testenv); +} + +#[test] +#[should_panic] +fn one_word() { + let testenv = create_one_word_envfile_testenv(); + test_in_env(&testenv, api_fn); +} + +#[test] +fn one_word_err() { + let testenv = create_one_word_envfile_testenv(); + test_err_line_parse(&testenv, ONE_WORD, ONE_WORD.len()); +} + +#[test] +fn one_line() { + let testenv = create_one_line_envfile_testenv(); + test_key_1_only(&testenv); +} + +#[test] +fn one_line_path() { + let testenv = create_one_line_envfile_testenv(); + test_default_envfile_path(&testenv); +} + +fn create_empty_envfile_testenv() -> TestEnv { + let mut testenv = create_testenv_with_default_var(); + testenv.add_envfile(".env", ""); + testenv +} + +fn create_one_word_envfile_testenv() -> TestEnv { + let mut testenv = create_testenv_with_default_var(); + testenv.add_envfile(".env", ONE_WORD); + testenv +} + +fn create_one_line_envfile_testenv() -> TestEnv { + TestEnv::init_with_envfile(KEYVAL_1) +} diff --git a/dotenv/tests/integration/case/export.rs b/dotenv/tests/integration/case/export.rs new file mode 100644 index 00000000..cb3df5e8 --- /dev/null +++ b/dotenv/tests/integration/case/export.rs @@ -0,0 +1,20 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn ignore_export() { + let testenv = TestEnv::init_with_envfile(format!("export {KEYVAL_1}")); + test_key_1_only(&testenv); +} + +#[test] +fn ignore_export_whitespace() { + let testenv = TestEnv::init_with_envfile(format!(" export {KEYVAL_1}")); + test_key_1_only(&testenv); +} + +#[test] +fn ignore_export_and_comment() { + let testenv = TestEnv::init_with_envfile(format!("export {KEYVAL_1} # {KEYVAL_2}")); + test_key_1_only(&testenv); +} diff --git a/dotenv/tests/integration/case/multiline.rs b/dotenv/tests/integration/case/multiline.rs new file mode 100644 index 00000000..4c780e42 --- /dev/null +++ b/dotenv/tests/integration/case/multiline.rs @@ -0,0 +1,62 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn no_quote_two_lines() { + let testenv = TestEnv::init_with_envfile("FOOBAR=foo\nbar"); + test_err_line_parse(&testenv, "bar", 3); +} + +#[test] +fn double_quote_two_lines_no_close() { + let line = "FOOBAR=\"foo\nbar"; + let testenv = TestEnv::init_with_envfile(line); + test_err_line_parse(&testenv, line, 15); +} + +#[test] +fn single_quote_two_lines_no_close() { + let line = "FOOBAR='foo\nbar"; + let testenv = TestEnv::init_with_envfile(line); + test_err_line_parse(&testenv, line, 15); +} + +#[test] +fn double_quote_two_lines() { + let envfile = r#"FOOBAR="foo +bar""#; + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, "FOOBAR", "foo\nbar"); +} + +#[test] +fn single_quote_two_lines() { + let testenv = TestEnv::init_with_envfile("FOOBAR='foo\nbar'"); + test_single_key_val(&testenv, "FOOBAR", "foo\nbar"); +} + +#[test] +fn double_quote_three_lines() { + let envfile = r#"FOOBAR="foo +bar +baz""#; + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, "FOOBAR", "foo\nbar\nbaz"); +} + +const COMPLEX_VALUE: &str = "-BEGIN PRIVATE KEY-\n-END PRIVATE KEY-\n\"QUOTED\""; +const COMPLEX_VALUE_ESCAPED: &str = "-BEGIN PRIVATE KEY-\n-END PRIVATE KEY-\\n\\\"QUOTED\\\""; + +#[test] +fn complex_escaped_in_double_quotes() { + let envfile = format!("WEAK=\"{COMPLEX_VALUE_ESCAPED}\""); + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, "WEAK", COMPLEX_VALUE); +} + +#[test] +fn complex_escaped_in_single_quotes() { + let envfile = format!("STRONG='{COMPLEX_VALUE_ESCAPED}'"); + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, "STRONG", COMPLEX_VALUE_ESCAPED); +} diff --git a/dotenv/tests/integration/case/multiline_comment.rs b/dotenv/tests/integration/case/multiline_comment.rs new file mode 100644 index 00000000..53bbaea8 --- /dev/null +++ b/dotenv/tests/integration/case/multiline_comment.rs @@ -0,0 +1,64 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn alone_single_quote() { + let testenv = + TestEnv::init_with_envfile(format!(r#"# {KEYVAL_1} Comment with single ' quote"#)); + test_keys_unset(&testenv) +} + +#[test] +fn alone_single_quote_with_space() { + let testenv = + TestEnv::init_with_envfile(format!(r#" # {KEYVAL_1} Comment with single ' quote"#)); + test_keys_unset(&testenv) +} + +#[test] +fn alone_double_quote() { + let testenv = + TestEnv::init_with_envfile(format!(r#"# {KEYVAL_1} Comment with double " quote"#)); + test_keys_unset(&testenv) +} + +#[test] +fn alone_double_quote_with_space() { + let testenv = + TestEnv::init_with_envfile(format!(r#" # {KEYVAL_1} Comment with double " quote"#)); + test_keys_unset(&testenv) +} + +#[test] +fn single_quote() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln(r#"# Comment with single ' quote"#); + test_open_quote_comment(efb); +} + +#[test] +fn single_quote_with_space() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln(r#" # Comment with single ' quote"#); + test_open_quote_comment(efb); +} + +#[test] +fn double_quote() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln(r#"# Comment with double " quote"#); + test_open_quote_comment(efb); +} + +#[test] +fn double_quote_with_space() { + let mut efb = EnvFileBuilder::new(); + efb.add_strln(r#" # Comment with double " quote"#); + test_open_quote_comment(efb); +} + +fn test_open_quote_comment(mut efb: EnvFileBuilder) { + efb.add_strln(KEYVAL_1); + let testenv = TestEnv::init_with_envfile(efb); + test_key_1_only(&testenv); +} diff --git a/dotenv/tests/integration/case/quote.rs b/dotenv/tests/integration/case/quote.rs new file mode 100644 index 00000000..4c04251d --- /dev/null +++ b/dotenv/tests/integration/case/quote.rs @@ -0,0 +1,89 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn double_value() { + let testenv = TestEnv::init_with_envfile(format!(r#"{KEY_1}="{VAL_1}""#)); + test_key_1_only(&testenv); +} + +#[test] +fn single_value() { + let testenv = TestEnv::init_with_envfile(format!(r"{KEY_1}='{VAL_1}'")); + test_key_1_only(&testenv); +} + +#[test] +fn double_and_single_value() { + let testenv = TestEnv::init_with_envfile(r#"fooo="'bar'""#); + test_single_key_val(&testenv, "fooo", "'bar'"); +} + +#[test] +fn single_and_double_value() { + let testenv = TestEnv::init_with_envfile(r#"fooo='"bar"'"#); + test_single_key_val(&testenv, "fooo", "\"bar\""); +} + +#[test] +fn double_key() { + let line = r#""FOOO"=bar"#; + let testenv = TestEnv::init_with_envfile(line); + test_err_line_parse(&testenv, line, 0); +} + +#[test] +fn single_key() { + let line = "'FOOO'=bar"; + let testenv = TestEnv::init_with_envfile(line); + test_err_line_parse(&testenv, line, 0); +} + +#[test] +fn double_in_double_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO="outer "inner"""#); + test_single_key_val(&testenv, "FOOO", "outer inner"); +} + +#[test] +fn double_in_single_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO='outer "inner"'"#); + test_single_key_val(&testenv, "FOOO", "outer \"inner\""); +} + +#[test] +fn single_in_double_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO="outer 'inner'""#); + test_single_key_val(&testenv, "FOOO", "outer 'inner'"); +} + +#[test] +fn single_in_single_value() { + let testenv = TestEnv::init_with_envfile("FOOO='outer 'inner''"); + test_single_key_val(&testenv, "FOOO", "outer inner"); +} + +#[test] +fn escaped_double_in_double_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO="outer \"inner\"""#); + test_single_key_val(&testenv, "FOOO", "outer \"inner\""); +} + +#[test] +fn escaped_double_in_single_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO='outer \"inner\"'"#); + test_single_key_val(&testenv, "FOOO", r#"outer \"inner\""#); +} + +#[test] +fn escaped_single_in_double_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO="outer \'inner\'""#); + test_single_key_val(&testenv, "FOOO", "outer 'inner'"); +} + +#[test] +fn escaped_single_in_single_value() { + let line = br"FOOO='outer \'inner\''"; + let testenv = TestEnv::init_with_envfile(*line); + test_err_line_parse(&testenv, "'outer \\'inner\\''", 16); +} diff --git a/dotenv/tests/integration/case/var_substitution.rs b/dotenv/tests/integration/case/var_substitution.rs new file mode 100644 index 00000000..2ebdb6b2 --- /dev/null +++ b/dotenv/tests/integration/case/var_substitution.rs @@ -0,0 +1,249 @@ +use crate::util::*; +use dotenvy_test_util::*; + +mod no_quotes { + use super::*; + + #[test] + fn from_env() { + let envfile = format!("{KEY_1}=${KEY_2}"); + test_key_1_set_val_2(&envfile); + } + + #[test] + fn plus_extra() { + let envfile = format!("{KEY_1}=${KEY_2}+extra"); + test_key_1_set_val_2_plus_extra(&envfile); + } + + #[test] + fn plus_space() { + let envfile = format!("{KEY_1}=${KEY_2} + extra"); + let testenv = create_testenv_with_key_2(&envfile); + let expected = format!("${KEY_2} + extra"); + test_err_line_parse(&testenv, &expected, 8); + } + + #[test] + fn braced() { + let envfile = format!("{KEY_1}=${{{KEY_2}}}"); + test_key_1_set_val_2(&envfile); + } + + #[test] + fn braced_plus() { + let envfile = format!("{KEY_1}=${{{KEY_2}}}1"); + let expected = format!("{VAL_2}1"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn escaped() { + let envfile = format!("{KEY_1}=\\${KEY_2}"); + let expected = format!("${KEY_2}"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn key_not_set() { + let envfile = format!("{KEY_1}=${KEY_2}"); + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, KEY_1, ""); + } + + #[test] + fn prev_entry() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}=${KEY_1}"); + let testenv = TestEnv::init_with_envfile(envfile); + test_env_vars(&testenv, &[(KEY_1, VAL_1), (KEY_2, VAL_1)]); + } + + #[test] + fn prev_entry_plus_extra() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}=${KEY_1}+extra"); + let testenv = TestEnv::init_with_envfile(envfile); + let expected_val_2 = format!("{VAL_1}+extra"); + test_env_vars(&testenv, &[(KEY_1, VAL_1), (KEY_2, &expected_val_2)]); + } +} + +mod double_quote { + use super::*; + + #[test] + fn from_env() { + let envfile = format!(r#"{KEY_1}="${KEY_2}"#); + let mut testenv = TestEnv::init_with_envfile(envfile.as_str()); + testenv.add_env_var(KEY_2, VAL_2); + test_err_line_parse(&testenv, &envfile, 11); + } + + #[test] + fn plus_extra() { + let envfile = format!(r#"{KEY_1}="${KEY_2}+extra""#); + test_key_1_set_val_2_plus_extra(&envfile); + } + + #[test] + fn plus_space() { + let envfile = format!(r#"{KEY_1}="${KEY_2} + extra""#); + test_key_1_set_val_2_plus_extra_with_space(&envfile); + } + + #[test] + fn braced() { + let envfile = format!("{KEY_1}=\"${{{KEY_2}}}\""); + test_key_1_set_val_2(&envfile); + } + + #[test] + fn braced_plus() { + let envfile = format!(r#"{KEY_1}="${{{KEY_2}}}1""#); + let expected = format!("{VAL_2}1"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn escaped() { + let envfile = format!(r#"{KEY_1}="\\${KEY_2}""#); + let testenv = create_testenv_with_key_2(&envfile); + let expected = format!(r#""\\${KEY_2}""#); + test_err_line_parse(&testenv, &expected, 8); + } + + #[test] + fn escaped_plus() { + let envfile = format!(r#"{KEY_1}="\\${KEY_2}+1""#); + let expected = format!("\\{VAL_2}+1"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn key_not_set() { + let envfile = format!(r#"{KEY_1}="${KEY_2}""#); + let testenv = TestEnv::init_with_envfile(envfile); + let expected = format!(r#""${KEY_2}""#); + test_err_line_parse(&testenv, &expected, 6); + } + + #[test] + fn key_not_set_plus_extra() { + let envfile = format!(r#"{KEY_1}="${KEY_2}+extra""#); + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, KEY_1, "+extra"); + } + + #[test] + fn prev_entry() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}=\"${KEY_1}\""); + let testenv = TestEnv::init_with_envfile(envfile); + let expected = format!(r#""${KEY_1}""#); + test_err_line_parse(&testenv, &expected, 6); + } + + #[test] + fn prev_entry_plus_extra() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}=\"${KEY_1}+extra\""); + let testenv = TestEnv::init_with_envfile(envfile); + let expected_val_2 = format!("{VAL_1}+extra"); + test_env_vars(&testenv, &[(KEY_1, VAL_1), (KEY_2, &expected_val_2)]); + } +} + +mod single_quote { + use super::*; + + #[test] + fn from_env() { + let envfile = format!("{KEY_1}='${KEY_2}'"); + let expected = format!("${KEY_2}"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn plus_extra() { + let envfile = format!("{KEY_1}='${KEY_2}+extra'"); + let expected = format!("${KEY_2}+extra"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn plus_space() { + let envfile = format!("{KEY_1}='${KEY_2} + extra'"); + let expected = format!("${KEY_2} + extra"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn braced() { + let envfile = format!("{KEY_1}='${{{KEY_2}}}'"); + let expected = format!("${{{KEY_2}}}"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn braced_plus() { + let envfile = format!("{KEY_1}='${{{KEY_2}}}1'"); + let expected = format!("${{{KEY_2}}}1"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn escaped() { + let envfile = format!("{KEY_1}='\\${KEY_2}'"); + let expected = format!("\\${KEY_2}"); + test_key_1_set_as_with_key_2(&envfile, &expected); + } + + #[test] + fn key_not_set() { + let envfile = format!("{KEY_1}='${KEY_2}'"); + let testenv = TestEnv::init_with_envfile(envfile); + let expected = format!("${KEY_2}"); + test_single_key_val(&testenv, KEY_1, &expected); + } + + #[test] + fn prev_entry() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}='${KEY_1}'"); + let testenv = TestEnv::init_with_envfile(envfile); + let expected = format!("${KEY_1}"); + test_env_vars(&testenv, &[(KEY_1, VAL_1), (KEY_2, &expected)]); + } + + #[test] + fn prev_entry_plus_extra() { + let envfile = format!("{KEYVAL_1}\n{KEY_2}='${KEY_1}+extra'"); + let testenv = TestEnv::init_with_envfile(envfile); + let expected = format!("${KEY_1}+extra"); + test_env_vars(&testenv, &[(KEY_1, VAL_1), (KEY_2, &expected)]); + } +} + +fn test_key_1_set_as_with_key_2(envfile: &str, expected: &str) { + let testenv = create_testenv_with_key_2(envfile); + test_single_key_val(&testenv, KEY_1, expected); +} + +fn test_key_1_set_val_2(envfile: &str) { + let testenv = create_testenv_with_key_2(envfile); + test_single_key_val(&testenv, KEY_1, VAL_2); +} + +fn test_key_1_set_val_2_plus_extra(envfile: &str) { + let testenv = create_testenv_with_key_2(envfile); + let exepcted = format!("{VAL_2}+extra"); + test_single_key_val(&testenv, KEY_1, &exepcted); +} + +fn test_key_1_set_val_2_plus_extra_with_space(envfile: &str) { + let testenv = create_testenv_with_key_2(envfile); + let exepcted = format!("{VAL_2} + extra"); + test_single_key_val(&testenv, KEY_1, &exepcted); +} + +fn create_testenv_with_key_2(envfile: &str) -> TestEnv { + let mut testenv = TestEnv::init_with_envfile(envfile); + testenv.add_env_var(KEY_2, VAL_2); + testenv +} diff --git a/dotenv/tests/integration/case/whitespace.rs b/dotenv/tests/integration/case/whitespace.rs new file mode 100644 index 00000000..4b99fdb8 --- /dev/null +++ b/dotenv/tests/integration/case/whitespace.rs @@ -0,0 +1,64 @@ +use crate::util::*; +use dotenvy_test_util::*; + +#[test] +fn ignore_left() { + let envfiles = [ + " FOOO=bar", + "\nFOOO=bar", + "\r\n FOOO=bar", + "\n\nFOOO=bar", + "\r\n\r\nFOOO=bar", + "\tFOOO=bar", + " \r\n\t FOOO=bar", + ]; + test_whitespace_envfiles(&envfiles, "FOOO", "bar"); +} + +#[test] +fn ignore_right() { + let envfiles = [ + "FOOO=bar ", + "FOOO=bar\n", + "FOOO=bar\n\n", + "FOOO=bar\r\n", + "FOOO=bar\r\n\r\n", + "FOOO=bar \t", + "FOOO=bar \t \n", + "FOOO=bar \t \r\n", + // TODO: This should be allowed. + // "FOOO=bar\t", + ]; + test_whitespace_envfiles(&envfiles, "FOOO", "bar"); +} + +#[test] +fn around_assignment() { + let testenv = TestEnv::init_with_envfile(format!("{KEY_1} = {VAL_1}")); + test_key_1_only(&testenv); +} + +#[test] +fn escaped_in_value() { + let testenv = TestEnv::init_with_envfile(r"FOOO=foo\ bar\ baz"); + test_single_key_val(&testenv, "FOOO", "foo bar baz"); +} + +#[test] +fn double_quoted_value() { + let testenv = TestEnv::init_with_envfile(r#"FOOO="foo bar baz""#); + test_single_key_val(&testenv, "FOOO", "foo bar baz"); +} + +#[test] +fn single_quoted_value() { + let testenv = TestEnv::init_with_envfile("FOOO='foo bar baz'"); + test_single_key_val(&testenv, "FOOO", "foo bar baz"); +} + +fn test_whitespace_envfiles(envfiles: &[&str], key: &str, expected: &str) { + for &envfile in envfiles { + let testenv = TestEnv::init_with_envfile(envfile); + test_single_key_val(&testenv, key, expected); + } +} diff --git a/dotenv/tests/integration/dotenv.rs b/dotenv/tests/integration/dotenv.rs deleted file mode 100644 index 3c95d826..00000000 --- a/dotenv/tests/integration/dotenv.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dotenvy_test_util::*; - -#[test] -fn dotenv_ok_default_env() { - test_in_default_env(|| { - dotenvy::dotenv().ok(); - assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_EXISTING_VALUE); - assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); - }); -} diff --git a/dotenv/tests/integration/main.rs b/dotenv/tests/integration/main.rs index 8c74edd4..deb5e351 100644 --- a/dotenv/tests/integration/main.rs +++ b/dotenv/tests/integration/main.rs @@ -1 +1,34 @@ -mod dotenv; +/// `dotenvy` integration tests +mod api { + mod dotenv; + mod dotenv_iter; + mod dotenv_override; + mod from_filename; + mod from_filename_iter; + mod from_filename_override; + mod from_path; + mod from_path_iter; + mod from_path_override; + mod from_read; + mod from_read_iter; + mod from_read_override; + mod var; + mod vars; +} + +/// different environment-setup test-cases +mod case { + mod bom; + mod comment; + mod directory; + mod envfile; + mod export; + mod multiline; + mod multiline_comment; + mod quote; + mod var_substitution; + mod whitespace; +} + +/// constructors, helpers, and assertions +pub(crate) mod util; diff --git a/dotenv/tests/integration/util/assert.rs b/dotenv/tests/integration/util/assert.rs new file mode 100644 index 00000000..5d46196b --- /dev/null +++ b/dotenv/tests/integration/util/assert.rs @@ -0,0 +1,37 @@ +use std::{io, path::Path}; + +use dotenvy::Error; +use dotenvy_test_util::*; + +pub fn assert_err_line_parse(line: &str, index: usize, actual: Error) { + match actual { + Error::LineParse(s, i) => { + assert_eq!(line, s, "expected line parse error for line `{line}`"); + assert_eq!(index, i, "expected line parse error at index {index}"); + } + _ => panic!("expected line parse error"), + } +} + +pub fn assert_err_not_found(actual: Error) { + match actual { + Error::Io(err) => assert_eq!(err.kind(), io::ErrorKind::NotFound), + _ => panic!("expected `NotFound` error"), + } +} + +pub fn assert_err_invalid_utf8(actual: Error) { + match actual { + Error::Io(err) => assert_eq!(err.kind(), io::ErrorKind::InvalidData), + _ => panic!("expected `InvalidData` error"), + } +} + +pub fn assert_default_envfile_path(testenv: &TestEnv, path: &Path) { + let expected = testenv + .temp_path() + .join(".env") + .canonicalize() + .expect("failed to canonicalize"); + assert_eq!(expected, path); +} diff --git a/dotenv/tests/integration/util/create.rs b/dotenv/tests/integration/util/create.rs new file mode 100644 index 00000000..e69de29b diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs new file mode 100644 index 00000000..abf3d61f --- /dev/null +++ b/dotenv/tests/integration/util/mod.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; + +use dotenvy::Error; +use dotenvy_test_util::*; + +/// common assertions +mod assert; +/// particular tests in a testenv +mod test; + +pub use assert::*; +pub use test::*; + +pub const KEY_1: &str = "FOOO"; +pub const VAL_1: &str = "bar"; +pub const KEYVAL_1: &str = "FOOO=bar"; + +pub const KEY_2: &str = "BARR"; +pub const VAL_2: &str = "foo"; +pub const KEYVAL_2: &str = "BARR=foo"; + +/// Call and unwrap the default api function +pub fn api_fn() { + dotenvy::dotenv().unwrap(); +} + +/// Call, unwrap and return the `PathBuf` of the default api function +pub fn api_fn_path() -> PathBuf { + dotenvy::dotenv().unwrap() +} + +/// Call, unwrap and return the `Error` of the default api function +pub fn api_fn_err() -> Error { + dotenvy::dotenv().unwrap_err() +} diff --git a/dotenv/tests/integration/util/test.rs b/dotenv/tests/integration/util/test.rs new file mode 100644 index 00000000..d496736e --- /dev/null +++ b/dotenv/tests/integration/util/test.rs @@ -0,0 +1,75 @@ +use super::*; + +pub fn test_default_envfile_path(testenv: &TestEnv) { + test_in_env(testenv, || { + let path = api_fn_path(); + assert_default_envfile_path(testenv, &path); + }) +} + +pub fn test_err_line_parse(testenv: &TestEnv, line: &str, index: usize) { + test_in_env(testenv, || { + let err = api_fn_err(); + assert_err_line_parse(line, index, err); + }) +} + +pub fn test_err_not_found(testenv: &TestEnv) { + test_in_env(testenv, || { + let err = api_fn_err(); + assert_err_not_found(err); + }) +} + +pub fn test_invalid_utf8(testenv: &TestEnv) { + test_in_env(testenv, || { + let err = api_fn_err(); + assert_err_invalid_utf8(err); + }) +} + +pub fn test_key_1_only(testenv: &TestEnv) { + test_in_env(testenv, || { + api_fn(); + assert_env_var(KEY_1, VAL_1); + assert_env_var_unset(KEY_2); + }); +} + +pub fn test_key_2_only(testenv: &TestEnv) { + test_in_env(testenv, || { + api_fn(); + assert_env_var_unset(KEY_1); + assert_env_var(KEY_2, VAL_2); + }); +} + +pub fn test_keys_unset(testenv: &TestEnv) { + test_in_env(testenv, || { + api_fn(); + assert_env_var_unset(KEY_1); + assert_env_var_unset(KEY_2); + }); +} + +pub fn test_key_1_with_hash_val(testenv: &TestEnv) { + test_in_env(testenv, || { + api_fn(); + assert_env_var(KEY_1, &format!("{VAL_1}#{KEYVAL_2}")); + assert_env_var_unset(KEY_2); + }); +} + +pub fn test_single_key_val(testenv: &TestEnv, key: &str, expected: &str) { + test_in_env(testenv, || { + api_fn(); + assert_env_var(key, expected); + }); +} + +pub fn test_env_vars(testenv: &TestEnv, vars: &[(&str, &str)]) { + test_in_env(testenv, || { + api_fn(); + assert_env_vars(vars) + }); +} From 2218aa9051017cd4ab75132f7291164ef9fe6898 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 23 Jul 2024 12:54:52 +0100 Subject: [PATCH 37/44] Test dotenv_iter with new harness --- dotenv/tests/integration/api/dotenv_iter.rs | 117 ++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/dotenv/tests/integration/api/dotenv_iter.rs b/dotenv/tests/integration/api/dotenv_iter.rs index 8b137891..f4491a28 100644 --- a/dotenv/tests/integration/api/dotenv_iter.rs +++ b/dotenv/tests/integration/api/dotenv_iter.rs @@ -1 +1,118 @@ +use crate::util::*; +use dotenvy::dotenv_iter; +use dotenvy_test_util::*; +use std::collections::HashMap; +#[test] +fn default_env_ok() { + test_in_default_env(|| { + dotenv_iter().ok(); + assert_default_existing_var(); + // the envfile shouldn't be loaded into the environment + assert_env_var_unset(DEFAULT_TEST_KEY); + }); +} + +#[test] +fn default_env_unwrap() { + test_in_default_env(|| { + dotenv_iter().unwrap(); + assert_default_existing_var(); + assert_env_var_unset(DEFAULT_TEST_KEY); + }); +} + +#[test] +fn no_envfile_ok() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + dotenv_iter().ok(); + assert_default_keys_unset(); + }); +} + +#[test] +fn no_envfile_err() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || match dotenv_iter() { + Ok(_) => panic!("should have failed"), + Err(err) => assert_err_not_found(err), + }); +} + +#[test] +fn no_vars() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + dotenv_iter().unwrap().for_each(|_| { + panic!("should have no keys"); + }); + }); +} + +#[test] +fn one_var() { + let testenv = TestEnv::init_with_envfile("FOOO=bar"); + test_in_env(&testenv, || { + let (key, value) = dotenv_iter_unwrap_one_item(); + assert_eq!(key, "FOOO"); + assert_eq!(value, "bar"); + }); +} + +#[test] +fn one_var_only() { + let testenv = TestEnv::init_with_envfile("FOOO=bar"); + test_in_env(&testenv, || { + let count = dotenv_iter().expect("valid file").count(); + assert_eq!(1, count); + }); +} + +#[test] +fn one_var_empty() { + let testenv = TestEnv::init_with_envfile("FOOO="); + test_in_env(&testenv, || { + let (key, value) = dotenv_iter_unwrap_one_item(); + assert_eq!(key, "FOOO"); + assert_eq!(value, ""); + }); +} + +#[test] +fn two_vars_into_hash_map() { + let vars = [("FOOO", "bar"), ("BAZ", "qux")]; + let envfile = create_custom_envfile(&vars); + let testenv = TestEnv::init_with_envfile(envfile); + + test_in_env(&testenv, || { + let map: HashMap = dotenv_iter() + .expect("valid file") + .map(|item| item.expect("valid item")) + .collect(); + + for (key, expected) in vars { + let actual = map.get(key).expect("valid key"); + assert_eq!(expected, actual); + } + }); +} + +#[test] +fn explicit_no_override() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("FOOO", "bar"); + testenv.add_envfile(".env", "FOOO=notbar"); + test_in_env(&testenv, || { + dotenv_iter().unwrap(); + assert_env_var("FOOO", "bar"); + }) +} + +fn dotenv_iter_unwrap_one_item() -> (String, String) { + dotenv_iter() + .expect("valid file") + .next() + .expect("one item") + .expect("valid item") +} From b5fd2c8f59bcb1c9c8536f4ef8b1ce2dbb5e15fd Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Tue, 23 Jul 2024 14:46:35 +0100 Subject: [PATCH 38/44] Test dotenv_override with new harness --- .../tests/integration/api/dotenv_override.rs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/dotenv/tests/integration/api/dotenv_override.rs b/dotenv/tests/integration/api/dotenv_override.rs index 8b137891..b7a514f7 100644 --- a/dotenv/tests/integration/api/dotenv_override.rs +++ b/dotenv/tests/integration/api/dotenv_override.rs @@ -1 +1,83 @@ +use crate::util::*; +use dotenvy::dotenv_override; +use dotenvy_test_util::*; +#[test] +fn no_file_ok() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + dotenv_override().ok(); + }); +} + +#[test] +fn no_file_err() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + let err = dotenv_override().unwrap_err(); + assert_err_not_found(err); + }); +} + +#[test] +fn empty_file_is_ok() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + assert!(dotenv_override().is_ok()); + }); +} + +#[test] +fn one_new_var() { + let testenv = TestEnv::init_with_envfile("FOOO=bar"); + test_in_env(&testenv, || { + dotenv_override().unwrap(); + assert_env_var("FOOO", "bar"); + }); +} + +#[test] +fn one_old_var() { + let mut testenv = TestEnv::init_with_envfile("FOOO=from_file"); + testenv.add_env_var("FOOO", "from_env"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "from_env"); + dotenv_override().unwrap(); + assert_env_var("FOOO", "from_file"); + }); +} + +#[test] +fn one_old_var_one_new_var() { + let vars = [("FOOO", "from_file"), ("BARR", "new")]; + let envfile = create_custom_envfile(&vars); + let mut testenv = TestEnv::init_with_envfile(envfile); + testenv.add_env_var("FOOO", "from_env"); + test_in_env(&testenv, || { + assert_env_var_unset("BARR"); + dotenv_override().unwrap(); + assert_env_vars(&vars); + }); +} + +#[test] +fn substitute_self() { + let mut testenv = TestEnv::init_with_envfile("FOOO=$FOOO+1"); + testenv.add_env_var("FOOO", "test"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "test"); + dotenv_override().unwrap(); + assert_env_var("FOOO", "test+1"); + }); +} + +#[test] +fn substitute_self_twice() { + let mut testenv = TestEnv::init_with_envfile("FOOO=$FOOO+1\nFOOO=$FOOO+1"); + testenv.add_env_var("FOOO", "test"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "test"); + dotenv_override().unwrap(); + assert_env_var("FOOO", "test+1+1"); + }); +} From 60139ae484f6973816afdc1543d2384901c045d1 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 24 Jul 2024 09:08:23 +0100 Subject: [PATCH 39/44] Test from_filename with new harness --- dotenv/tests/integration/api/from_filename.rs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/dotenv/tests/integration/api/from_filename.rs b/dotenv/tests/integration/api/from_filename.rs index 8b137891..adb26128 100644 --- a/dotenv/tests/integration/api/from_filename.rs +++ b/dotenv/tests/integration/api/from_filename.rs @@ -1 +1,112 @@ +use crate::util::*; +use dotenvy::from_filename; +use dotenvy_test_util::*; +#[test] +fn no_file() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + let err = from_filename("nonexistent.env").unwrap_err(); + assert_err_not_found(err); + }); +} + +#[test] +fn empty_default_file() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + assert!(from_filename(".env").is_ok()); + }); +} + +#[test] +fn empty_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", ""); + test_in_env(&testenv, || { + assert!(from_filename(".custom.env").is_ok()); + }); +} + +#[test] +fn default_file_not_read_on_missing_file() { + test_in_default_env(|| { + let err = from_filename("nonexistent.env").unwrap_err(); + assert_err_not_found(err); + assert_env_var_unset(DEFAULT_TEST_KEY); + }) +} + +#[test] +fn dotenv_then_custom() { + let mut testenv = TestEnv::default(); + testenv.add_envfile("custom", KEYVAL_1); + test_in_env(&testenv, || { + dotenvy::dotenv().unwrap(); + from_filename("custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + assert_default_keys(); + }); +} + +#[test] +fn dotenv_then_custom_no_override() { + let mut testenv = TestEnv::default(); + testenv.add_envfile("custom", format!("{DEFAULT_TEST_KEY}=from_custom")); + test_in_env(&testenv, || { + dotenvy::dotenv().unwrap(); + from_filename("custom").unwrap(); + assert_default_keys(); + }); +} + +#[test] +fn explicit_no_override() { + let mut testenv = TestEnv::init(); + testenv.add_env_var(KEY_1, VAL_1); + testenv.add_envfile("custom", format!("{KEY_1}=from_custom")); + test_in_env(&testenv, || { + from_filename("custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn child_dir() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("child/custom", KEYVAL_1); + test_in_env(&testenv, || { + from_filename("child/custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn parent_dir_relative_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + from_filename("../custom.env").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn parent_dir_absolute_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + let path = testenv + .temp_path() + .join("custom.env") + .canonicalize() + .expect("canonicalize envfile"); + from_filename(path).unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} From c84659545032a5d078de1628bf8edf4b4bb07641 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 24 Jul 2024 10:17:28 +0100 Subject: [PATCH 40/44] Test from_filename_iter with new harness --- dotenv/tests/integration/api/dotenv_iter.rs | 17 +-- dotenv/tests/integration/api/from_filename.rs | 6 +- .../integration/api/from_filename_iter.rs | 102 ++++++++++++++++++ dotenv/tests/integration/util/mod.rs | 38 ++++++- 4 files changed, 140 insertions(+), 23 deletions(-) diff --git a/dotenv/tests/integration/api/dotenv_iter.rs b/dotenv/tests/integration/api/dotenv_iter.rs index f4491a28..c0c6ea9b 100644 --- a/dotenv/tests/integration/api/dotenv_iter.rs +++ b/dotenv/tests/integration/api/dotenv_iter.rs @@ -1,7 +1,6 @@ use crate::util::*; use dotenvy::dotenv_iter; use dotenvy_test_util::*; -use std::collections::HashMap; #[test] fn default_env_ok() { @@ -81,21 +80,7 @@ fn one_var_empty() { #[test] fn two_vars_into_hash_map() { - let vars = [("FOOO", "bar"), ("BAZ", "qux")]; - let envfile = create_custom_envfile(&vars); - let testenv = TestEnv::init_with_envfile(envfile); - - test_in_env(&testenv, || { - let map: HashMap = dotenv_iter() - .expect("valid file") - .map(|item| item.expect("valid item")) - .collect(); - - for (key, expected) in vars { - let actual = map.get(key).expect("valid key"); - assert_eq!(expected, actual); - } - }); + check_iter_default_envfile_into_hash_map(dotenv_iter); } #[test] diff --git a/dotenv/tests/integration/api/from_filename.rs b/dotenv/tests/integration/api/from_filename.rs index adb26128..8102f71a 100644 --- a/dotenv/tests/integration/api/from_filename.rs +++ b/dotenv/tests/integration/api/from_filename.rs @@ -101,11 +101,7 @@ fn parent_dir_absolute_path() { testenv.add_envfile("custom.env", KEYVAL_1); testenv.set_work_dir("child"); test_in_env(&testenv, || { - let path = testenv - .temp_path() - .join("custom.env") - .canonicalize() - .expect("canonicalize envfile"); + let path = canonicalize_envfile_path(&testenv, "custom.env"); from_filename(path).unwrap(); assert_env_var(KEY_1, VAL_1); }); diff --git a/dotenv/tests/integration/api/from_filename_iter.rs b/dotenv/tests/integration/api/from_filename_iter.rs index 8b137891..c9f452e6 100644 --- a/dotenv/tests/integration/api/from_filename_iter.rs +++ b/dotenv/tests/integration/api/from_filename_iter.rs @@ -1 +1,103 @@ +use std::path::Path; +use crate::util::*; +use dotenvy::from_filename_iter; +use dotenvy_test_util::*; + +#[test] +fn no_file_ok() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + from_filename_iter("nonexistent").ok(); + }); +} + +#[test] +fn no_file_err() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || match from_filename_iter("nonexistent") { + Ok(_) => panic!("expected error"), + Err(err) => assert_err_not_found(err), + }); +} + +#[test] +fn empty_default_file() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + from_filename_iter(".env").unwrap().for_each(|_| { + panic!("should have no keys"); + }); + }); +} + +#[test] +fn empty_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", ""); + test_in_env(&testenv, || { + from_filename_iter(".custom.env").unwrap().for_each(|_| { + panic!("should have no keys"); + }); + }); +} + +#[test] +fn default_file_not_read_on_missing_file() { + test_in_default_env(|| { + from_filename_iter("nonexistent.env").ok(); + assert_env_var_unset(DEFAULT_TEST_KEY); + }) +} + +#[test] +fn child_dir() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("child/custom", KEYVAL_1); + test_in_env(&testenv, || { + assert_single_key_file("child/custom"); + }); +} + +#[test] +fn parent_dir_relative_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + assert_single_key_file("../custom.env"); + }); +} + +#[test] +fn parent_dir_absolute_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + let path = canonicalize_envfile_path(&testenv, "custom.env"); + assert_single_key_file(path); + }); +} + +#[test] +fn two_vars_into_hash_map() { + check_iter_default_envfile_into_hash_map(|| from_filename_iter(".env")); +} + +fn assert_single_key_file(path: impl AsRef) { + let (key, value) = from_filename_iter_unwrap_one_item(path); + assert_eq!(key, KEY_1); + assert_eq!(value, VAL_1); +} + +fn from_filename_iter_unwrap_one_item(path: impl AsRef) -> (String, String) { + from_filename_iter(path) + .expect("valid file") + .next() + .expect("one item") + .expect("valid item") +} diff --git a/dotenv/tests/integration/util/mod.rs b/dotenv/tests/integration/util/mod.rs index abf3d61f..77d3b460 100644 --- a/dotenv/tests/integration/util/mod.rs +++ b/dotenv/tests/integration/util/mod.rs @@ -1,6 +1,10 @@ -use std::path::PathBuf; +use std::{ + collections::HashMap, + fs::File, + path::{Path, PathBuf}, +}; -use dotenvy::Error; +use dotenvy::{Error, Iter, Result}; use dotenvy_test_util::*; /// common assertions @@ -33,3 +37,33 @@ pub fn api_fn_path() -> PathBuf { pub fn api_fn_err() -> Error { dotenvy::dotenv().unwrap_err() } + +pub fn check_iter_default_envfile_into_hash_map(iter_fn: F) +where + F: FnOnce() -> Result>, +{ + let vars = [("FOOO", "bar"), ("BAZ", "qux")]; + let envfile = create_custom_envfile(&vars); + let testenv = TestEnv::init_with_envfile(envfile); + + test_in_env(&testenv, || { + let map: HashMap = iter_fn() + .expect("valid file") + .map(|item| item.expect("valid item")) + .collect(); + + for (key, expected) in vars { + let actual = map.get(key).expect("valid key"); + assert_eq!(expected, actual); + } + }); +} + +/// Relative to the temp dir +pub fn canonicalize_envfile_path(testenv: &TestEnv, envfile: impl AsRef) -> PathBuf { + testenv + .temp_path() + .join(envfile.as_ref()) + .canonicalize() + .expect("canonicalize envfile") +} From 992e111772c299cbf943f5bec00e2878dce11937 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 24 Jul 2024 14:42:28 +0100 Subject: [PATCH 41/44] Test from_filename_override with new harness --- .../integration/api/from_filename_override.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/dotenv/tests/integration/api/from_filename_override.rs b/dotenv/tests/integration/api/from_filename_override.rs index 8b137891..16785590 100644 --- a/dotenv/tests/integration/api/from_filename_override.rs +++ b/dotenv/tests/integration/api/from_filename_override.rs @@ -1 +1,94 @@ +use crate::util::*; +use dotenvy::from_filename_override; +use dotenvy_test_util::*; +#[test] +fn no_file_ok() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + from_filename_override("nonexistent").ok(); + }); +} + +#[test] +fn no_file_err() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + let err = from_filename_override("nonexistent.env").unwrap_err(); + assert_err_not_found(err); + }); +} + +#[test] +fn empty_default_file() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + assert!(from_filename_override(".env").is_ok()); + }); +} + +#[test] +fn empty_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", ""); + test_in_env(&testenv, || { + assert!(from_filename_override(".custom.env").is_ok()); + }); +} + +#[test] +fn one_var_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", KEYVAL_1); + test_in_env(&testenv, || { + assert!(from_filename_override(".custom.env").is_ok()); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn override_existing_var_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("FOOO", "from_env"); + testenv.add_envfile(".custom.env", "FOOO=from_file"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "from_env"); + from_filename_override(".custom.env").unwrap(); + assert_env_var("FOOO", "from_file"); + }); +} + +#[test] +fn default_override() { + test_in_default_env(|| { + from_filename_override(".env").unwrap(); + assert_env_var(DEFAULT_TEST_KEY, DEFAULT_TEST_VALUE); + assert_env_var(DEFAULT_EXISTING_KEY, DEFAULT_OVERRIDING_VALUE); + }) +} + +#[test] +fn substitute_self() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("FOOO", "test"); + testenv.add_envfile(".custom.env", "FOOO=$FOOO+1"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "test"); + from_filename_override(".custom.env").unwrap(); + assert_env_var("FOOO", "test+1"); + }); +} + +#[test] +fn substitute_self_two_files() { + let mut testenv = TestEnv::init(); + testenv.add_env_var("FOOO", "test"); + testenv.add_envfile(".custom1.env", "FOOO=$FOOO+1"); + testenv.add_envfile(".custom2.env", "FOOO=$FOOO+1"); + test_in_env(&testenv, || { + assert_env_var("FOOO", "test"); + from_filename_override(".custom1.env").unwrap(); + from_filename_override(".custom2.env").unwrap(); + assert_env_var("FOOO", "test+1+1"); + }); +} From d6a9d8295a6a9f8346e3978accb72b433db98e38 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Wed, 24 Jul 2024 16:59:32 +0100 Subject: [PATCH 42/44] Test from_filename* return value --- dotenv/tests/integration/api/from_filename.rs | 9 +++++++++ dotenv/tests/integration/api/from_filename_override.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/dotenv/tests/integration/api/from_filename.rs b/dotenv/tests/integration/api/from_filename.rs index 8102f71a..d5f82c82 100644 --- a/dotenv/tests/integration/api/from_filename.rs +++ b/dotenv/tests/integration/api/from_filename.rs @@ -28,6 +28,15 @@ fn empty_custom_file() { }); } +#[test] +fn return_path_valid() { + let testenv = TestEnv::default(); + test_in_env(&testenv, || { + let actual = from_filename(".env").unwrap(); + assert_default_envfile_path(&testenv, &actual); + }); +} + #[test] fn default_file_not_read_on_missing_file() { test_in_default_env(|| { diff --git a/dotenv/tests/integration/api/from_filename_override.rs b/dotenv/tests/integration/api/from_filename_override.rs index 16785590..b1bf54c8 100644 --- a/dotenv/tests/integration/api/from_filename_override.rs +++ b/dotenv/tests/integration/api/from_filename_override.rs @@ -36,6 +36,15 @@ fn empty_custom_file() { }); } +#[test] +fn return_path_valid() { + let testenv = TestEnv::default(); + test_in_env(&testenv, || { + let actual = from_filename_override(".env").unwrap(); + assert_default_envfile_path(&testenv, &actual); + }); +} + #[test] fn one_var_custom_file() { let mut testenv = TestEnv::init(); From fb8dbb0035471428061871b250c6a682bf251013 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Thu, 25 Jul 2024 12:18:20 +0100 Subject: [PATCH 43/44] Test from_path with new harness --- dotenv/tests/integration/api/from_path.rs | 107 ++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/dotenv/tests/integration/api/from_path.rs b/dotenv/tests/integration/api/from_path.rs index 8b137891..3159eeba 100644 --- a/dotenv/tests/integration/api/from_path.rs +++ b/dotenv/tests/integration/api/from_path.rs @@ -1 +1,108 @@ +use crate::util::*; +use dotenvy::from_path; +use dotenvy_test_util::*; +#[test] +fn no_file() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + let err = from_path("nonexistent.env").unwrap_err(); + assert_err_not_found(err); + }); +} + +#[test] +fn empty_default_file() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + assert!(from_path(".env").is_ok()); + }); +} + +#[test] +fn empty_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", ""); + test_in_env(&testenv, || { + assert!(from_path(".custom.env").is_ok()); + }); +} + +#[test] +fn default_file_not_read_on_missing_file() { + test_in_default_env(|| { + let err = from_path("nonexistent.env").unwrap_err(); + assert_err_not_found(err); + assert_env_var_unset(DEFAULT_TEST_KEY); + }) +} + +#[test] +fn dotenv_then_custom() { + let mut testenv = TestEnv::default(); + testenv.add_envfile("custom", KEYVAL_1); + test_in_env(&testenv, || { + dotenvy::dotenv().unwrap(); + from_path("custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + assert_default_keys(); + }); +} + +#[test] +fn dotenv_then_custom_no_override() { + let mut testenv = TestEnv::default(); + testenv.add_envfile("custom", format!("{DEFAULT_TEST_KEY}=from_custom")); + test_in_env(&testenv, || { + dotenvy::dotenv().unwrap(); + from_path("custom").unwrap(); + assert_default_keys(); + }); +} + +#[test] +fn explicit_no_override() { + let mut testenv = TestEnv::init(); + testenv.add_env_var(KEY_1, VAL_1); + testenv.add_envfile("custom", format!("{KEY_1}=from_custom")); + test_in_env(&testenv, || { + from_path("custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn child_dir() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("child/custom", KEYVAL_1); + test_in_env(&testenv, || { + from_path("child/custom").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn parent_dir_relative_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + from_path("../custom.env").unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} + +#[test] +fn parent_dir_absolute_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + let path = canonicalize_envfile_path(&testenv, "custom.env"); + from_path(path).unwrap(); + assert_env_var(KEY_1, VAL_1); + }); +} From 627ec7516afbf3af13214dcacaee69ef2395d259 Mon Sep 17 00:00:00 2001 From: Christopher Morton Date: Thu, 25 Jul 2024 12:33:11 +0100 Subject: [PATCH 44/44] Test from_path_iter with new harness --- .../tests/integration/api/from_path_iter.rs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/dotenv/tests/integration/api/from_path_iter.rs b/dotenv/tests/integration/api/from_path_iter.rs index 8b137891..1bca5958 100644 --- a/dotenv/tests/integration/api/from_path_iter.rs +++ b/dotenv/tests/integration/api/from_path_iter.rs @@ -1 +1,103 @@ +use std::path::Path; +use crate::util::*; +use dotenvy::from_path_iter; +use dotenvy_test_util::*; + +#[test] +fn no_file_ok() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || { + from_path_iter("nonexistent").ok(); + }); +} + +#[test] +fn no_file_err() { + let testenv = TestEnv::init(); + test_in_env(&testenv, || match from_path_iter("nonexistent") { + Ok(_) => panic!("expected error"), + Err(err) => assert_err_not_found(err), + }); +} + +#[test] +fn empty_default_file() { + let testenv = TestEnv::init_with_envfile(""); + test_in_env(&testenv, || { + from_path_iter(".env").unwrap().for_each(|_| { + panic!("should have no keys"); + }); + }); +} + +#[test] +fn empty_custom_file() { + let mut testenv = TestEnv::init(); + testenv.add_envfile(".custom.env", ""); + test_in_env(&testenv, || { + from_path_iter(".custom.env").unwrap().for_each(|_| { + panic!("should have no keys"); + }); + }); +} + +#[test] +fn default_file_not_read_on_missing_file() { + test_in_default_env(|| { + from_path_iter("nonexistent.env").ok(); + assert_env_var_unset(DEFAULT_TEST_KEY); + }) +} + +#[test] +fn child_dir() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("child/custom", KEYVAL_1); + test_in_env(&testenv, || { + assert_single_key_file("child/custom"); + }); +} + +#[test] +fn parent_dir_relative_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + assert_single_key_file("../custom.env"); + }); +} + +#[test] +fn parent_dir_absolute_path() { + let mut testenv = TestEnv::init(); + testenv.add_child_dir("child"); + testenv.add_envfile("custom.env", KEYVAL_1); + testenv.set_work_dir("child"); + test_in_env(&testenv, || { + let path = canonicalize_envfile_path(&testenv, "custom.env"); + assert_single_key_file(path); + }); +} + +#[test] +fn two_vars_into_hash_map() { + check_iter_default_envfile_into_hash_map(|| from_path_iter(".env")); +} + +fn assert_single_key_file(path: impl AsRef) { + let (key, value) = from_path_iter_unwrap_one_item(path); + assert_eq!(key, KEY_1); + assert_eq!(value, VAL_1); +} + +fn from_path_iter_unwrap_one_item(path: impl AsRef) -> (String, String) { + from_path_iter(path) + .expect("valid file") + .next() + .expect("one item") + .expect("valid item") +}