diff --git a/Cargo.toml b/Cargo.toml index 09f3698d..0b166e89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ jwt-openid = ["jwt"] cli = ["dep:clap"] otel = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-otlp", "dep:tracing-opentelemetry"] grpc = ["dep:tonic"] +testing = ["dep:insta", "dep:rstest"] [dependencies] # Config @@ -75,6 +76,10 @@ clap = { workspace = true, features = ["derive", "string"], optional = true } # gRPC tonic = { workspace = true, optional = true } +# Testing +insta = { workspace = true, optional = true } +rstest = { workspace = true, optional = true } + # Others anyhow = { workspace = true } serde = { workspace = true } @@ -87,7 +92,7 @@ serde_json = "1.0.96" toml = "0.8.0" url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.1.2", features = ["v4", "serde"] } -futures = "0.3.19" +futures = "0.3.21" futures-core = "0.3.28" chrono = { version = "0.4.34", features = ["serde"] } byte-unit = { version = "5.0.0", features = ["serde"] } @@ -103,10 +108,10 @@ time = "0.3.36" [dev-dependencies] cargo-husky = { version = "1.5.0", default-features = false, features = ["user-hooks"] } -insta = { version = "1.39.0", features = ["toml"] } +insta = { workspace = true } mockall = "0.12.1" mockall_double = "0.3.1" -rstest = "0.21.0" +rstest = { workspace = true } [workspace] members = [".", "examples/*"] @@ -131,6 +136,10 @@ tonic = { version = "0.11.0" } # Todo: the default `rss-stats` feature has a dependency that currently can't be satisfied (memchr: ~2.3) rusty-sidekiq = { version = "0.10.5", default-features = false } +# Testing +insta = { version = "1.39.0", features = ["toml", "filters"] } +rstest = { version = "0.21.0" } + # Others # Todo: minimize tokio features included in `roadster` tokio = { version = "1.28.0", features = ["full"] } diff --git a/src/api/cli/mod.rs b/src/api/cli/mod.rs index 591237d3..8518588f 100644 --- a/src/api/cli/mod.rs +++ b/src/api/cli/mod.rs @@ -155,7 +155,7 @@ mockall::mock! { mod tests { use super::*; use crate::app::MockApp; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use itertools::Itertools; use rstest::{fixture, rstest}; diff --git a/src/config/app_config.rs b/src/config/app_config.rs index eb81b3d2..278cd80e 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -6,7 +6,7 @@ use crate::config::health_check::HealthCheck; use crate::config::service::Service; use crate::config::tracing::Tracing; use crate::error::RoadsterResult; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use config::builder::DefaultState; use config::{Case, Config, ConfigBuilder, FileFormat}; use dotenvy::dotenv; diff --git a/src/config/auth/mod.rs b/src/config/auth/mod.rs index 5f6e371e..874d3ab9 100644 --- a/src/config/auth/mod.rs +++ b/src/config/auth/mod.rs @@ -1,4 +1,4 @@ -use crate::util::serde_util::UriOrString; +use crate::util::serde::UriOrString; use serde_derive::{Deserialize, Serialize}; use validator::Validate; @@ -35,7 +35,7 @@ pub struct JwtClaims { #[cfg(test)] mod tests { use super::*; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use rstest::{fixture, rstest}; diff --git a/src/config/database/mod.rs b/src/config/database/mod.rs index 73138698..fff58e30 100644 --- a/src/config/database/mod.rs +++ b/src/config/database/mod.rs @@ -68,7 +68,7 @@ impl From<&Database> for ConnectOptions { #[cfg(test)] mod deserialize_tests { use super::*; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::{assert_debug_snapshot, assert_toml_snapshot}; use rstest::{fixture, rstest}; diff --git a/src/config/health_check/mod.rs b/src/config/health_check/mod.rs index 822e8cb9..34520010 100644 --- a/src/config/health_check/mod.rs +++ b/src/config/health_check/mod.rs @@ -1,6 +1,6 @@ use crate::app::context::AppContext; use crate::config::app_config::CustomConfig; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use axum::extract::FromRef; use config::{FileFormat, FileSourceString}; use serde_derive::{Deserialize, Serialize}; diff --git a/src/config/service/http/default_routes.rs b/src/config/service/http/default_routes.rs index 271753d9..f32e699f 100644 --- a/src/config/service/http/default_routes.rs +++ b/src/config/service/http/default_routes.rs @@ -1,5 +1,5 @@ use crate::app::context::AppContext; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use axum::extract::FromRef; use serde_derive::{Deserialize, Serialize}; use validator::Validate; diff --git a/src/config/service/http/initializer.rs b/src/config/service/http/initializer.rs index db128f64..af7e066e 100644 --- a/src/config/service/http/initializer.rs +++ b/src/config/service/http/initializer.rs @@ -1,7 +1,7 @@ use crate::app::context::AppContext; use crate::config::app_config::CustomConfig; use crate::service::http::initializer::normalize_path::NormalizePathConfig; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use axum::extract::FromRef; use serde_derive::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/src/config/service/http/middleware.rs b/src/config/service/http/middleware.rs index 6814db1a..2d7c7c6a 100644 --- a/src/config/service/http/middleware.rs +++ b/src/config/service/http/middleware.rs @@ -12,7 +12,7 @@ use crate::service::http::middleware::sensitive_headers::{ use crate::service::http::middleware::size_limit::SizeLimitConfig; use crate::service::http::middleware::timeout::TimeoutConfig; use crate::service::http::middleware::tracing::TracingConfig; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use axum::extract::FromRef; use serde_derive::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/src/config/service/mod.rs b/src/config/service/mod.rs index 0c15959e..57b3d65c 100644 --- a/src/config/service/mod.rs +++ b/src/config/service/mod.rs @@ -12,7 +12,7 @@ use crate::config::service::grpc::GrpcServiceConfig; use crate::config::service::http::HttpServiceConfig; #[cfg(feature = "sidekiq")] use crate::config::service::worker::sidekiq::SidekiqServiceConfig; -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use serde_derive::{Deserialize, Serialize}; use validator::Validate; diff --git a/src/config/service/worker/sidekiq/mod.rs b/src/config/service/worker/sidekiq/mod.rs index c3fb516c..c3ca3e9c 100644 --- a/src/config/service/worker/sidekiq/mod.rs +++ b/src/config/service/worker/sidekiq/mod.rs @@ -106,7 +106,7 @@ pub struct ConnectionPool { #[cfg(test)] mod deserialize_tests { use super::*; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use rstest::{fixture, rstest}; diff --git a/src/config/tracing/mod.rs b/src/config/tracing/mod.rs index 51c9a19f..1c8ab605 100644 --- a/src/config/tracing/mod.rs +++ b/src/config/tracing/mod.rs @@ -1,5 +1,5 @@ #[cfg(feature = "otel")] -use crate::util::serde_util::default_true; +use crate::util::serde::default_true; use config::{FileFormat, FileSourceString}; use serde_derive::{Deserialize, Serialize}; use strum_macros::{EnumString, IntoStaticStr}; @@ -50,7 +50,7 @@ pub enum Format { #[cfg(all(test, feature = "otel"))] mod deserialize_tests { use super::*; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use rstest::{fixture, rstest}; diff --git a/src/health_check/default.rs b/src/health_check/default.rs index 2a47f96f..96518cd4 100644 --- a/src/health_check/default.rs +++ b/src/health_check/default.rs @@ -37,7 +37,7 @@ pub fn default_health_checks( mod tests { use crate::app::context::AppContext; use crate::config::app_config::AppConfig; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use bb8::Pool; use insta::assert_toml_snapshot; use itertools::Itertools; diff --git a/src/lib.rs b/src/lib.rs index 8486cbdb..af8a062d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,5 +23,7 @@ pub mod error; pub mod health_check; pub mod middleware; pub mod service; +#[cfg(any(test, feature = "testing"))] +pub mod testing; pub mod tracing; pub mod util; diff --git a/src/middleware/http/auth/jwt/ietf.rs b/src/middleware/http/auth/jwt/ietf.rs index 6e923e56..e5ff0a47 100644 --- a/src/middleware/http/auth/jwt/ietf.rs +++ b/src/middleware/http/auth/jwt/ietf.rs @@ -1,5 +1,5 @@ use crate::middleware::http::auth::jwt::Subject; -use crate::util::serde_util::UriOrString; +use crate::util::serde::UriOrString; use chrono::serde::ts_seconds; use chrono::{DateTime, Utc}; use serde_derive::{Deserialize, Serialize}; @@ -55,7 +55,7 @@ mod tests { use super::*; use crate::error::RoadsterResult; use crate::middleware::http::auth::jwt::decode_auth_token; - use crate::util::serde_util::{UriOrString, Wrapper}; + use crate::util::serde::{UriOrString, Wrapper}; use chrono::{TimeDelta, Utc}; use jsonwebtoken::{encode, EncodingKey, Header, TokenData}; use serde_json::from_str; diff --git a/src/middleware/http/auth/jwt/mod.rs b/src/middleware/http/auth/jwt/mod.rs index 8b14eaee..848808e5 100644 --- a/src/middleware/http/auth/jwt/mod.rs +++ b/src/middleware/http/auth/jwt/mod.rs @@ -9,7 +9,7 @@ use crate::error::{Error, RoadsterResult}; use crate::middleware::http::auth::jwt::ietf::Claims; #[cfg(all(feature = "jwt-openid", not(feature = "jwt-ietf")))] use crate::middleware::http::auth::jwt::openid::Claims; -use crate::util::serde_util::{deserialize_from_str, serialize_to_str}; +use crate::util::serde::{deserialize_from_str, serialize_to_str}; #[cfg(feature = "open-api")] use aide::OperationInput; use async_trait::async_trait; @@ -130,7 +130,7 @@ pub enum Subject { #[cfg(test)] mod tests { use super::*; - use crate::util::serde_util::Wrapper; + use crate::util::serde::Wrapper; use serde_json::from_str; use std::str::FromStr; use url::Url; diff --git a/src/middleware/http/auth/jwt/openid.rs b/src/middleware/http/auth/jwt/openid.rs index 108eaef2..eb1c5064 100644 --- a/src/middleware/http/auth/jwt/openid.rs +++ b/src/middleware/http/auth/jwt/openid.rs @@ -7,7 +7,7 @@ use serde_with::serde_as; use std::collections::BTreeMap; use url::Url; -use crate::util::serde_util::{deserialize_from_str, serialize_to_str, UriOrString}; +use crate::util::serde::{deserialize_from_str, serialize_to_str, UriOrString}; /// JWT Claims. Provides fields for the default/recommended registered claim names. Additional /// claim names are collected in the `custom` map. @@ -71,7 +71,7 @@ pub enum Acr { #[cfg(test)] mod tests { use super::*; - use crate::util::serde_util::Wrapper; + use crate::util::serde::Wrapper; use serde_json::from_str; use std::str::FromStr; use url::Url; diff --git a/src/service/http/middleware/cors.rs b/src/service/http/middleware/cors.rs index 379becbf..94c37aba 100644 --- a/src/service/http/middleware/cors.rs +++ b/src/service/http/middleware/cors.rs @@ -286,8 +286,8 @@ where mod tests { use super::*; use crate::config::app_config::AppConfig; - use crate::util::serde_util::Wrapper; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; + use crate::util::serde::Wrapper; use insta::assert_toml_snapshot; use rstest::{fixture, rstest}; diff --git a/src/service/http/middleware/default.rs b/src/service/http/middleware/default.rs index 5eda7eef..8856cb83 100644 --- a/src/service/http/middleware/default.rs +++ b/src/service/http/middleware/default.rs @@ -43,7 +43,7 @@ where mod tests { use crate::app::context::AppContext; use crate::config::app_config::AppConfig; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use itertools::Itertools; use rstest::{fixture, rstest}; diff --git a/src/service/worker/sidekiq/app_worker.rs b/src/service/worker/sidekiq/app_worker.rs index 31c19338..d6b6e249 100644 --- a/src/service/worker/sidekiq/app_worker.rs +++ b/src/service/worker/sidekiq/app_worker.rs @@ -133,7 +133,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::util::serde_util::Wrapper; + use crate::util::serde::Wrapper; use serde_json::from_str; #[test] @@ -179,7 +179,7 @@ mod tests { #[cfg(test)] mod deserialize_tests { use super::*; - use crate::util::test_util::TestCase; + use crate::testing::snapshot::TestCase; use insta::assert_toml_snapshot; use rstest::{fixture, rstest}; diff --git a/src/testing/mod.rs b/src/testing/mod.rs new file mode 100644 index 00000000..8d263be8 --- /dev/null +++ b/src/testing/mod.rs @@ -0,0 +1 @@ +pub mod snapshot; diff --git a/src/testing/snapshot.rs b/src/testing/snapshot.rs new file mode 100644 index 00000000..70298f78 --- /dev/null +++ b/src/testing/snapshot.rs @@ -0,0 +1,270 @@ +//! Utilities for modifying `insta` snapshot [Settings]. + +use crate::util::regex::UUID_REGEX; +use insta::internals::SettingsBindDropGuard; +use insta::Settings; +use std::thread::current; +use typed_builder::TypedBuilder; + +/// Configure which settings to apply on the snapshot [Settings]. +/// +/// When built, a [TestCase] is returned. +#[derive(TypedBuilder)] +#[builder(build_method(into = TestCase))] +#[non_exhaustive] +pub struct TestCaseConfig { + /// The [Settings] to modify. If not provided, will use `Settings::clone_current()`. + #[builder(default, setter(strip_option))] + pub settings: Option, + + /// The description of the test case. If not provided, will be extracted from the name of + /// the current thread, which is based on the test case name. + /// + /// This is particularly useful when using `insta` together with `rstest`. + /// See: + /// + /// # Examples + /// + /// ## [TestCase] description for `rstest` cases + /// ```rust + /// #[cfg(test)] + /// mod tests { + /// use insta::assert_snapshot; + /// use rstest::{fixture, rstest}; + /// use roadster::testing::snapshot::TestCase; + /// + /// #[fixture] + /// fn case() -> TestCase { + /// Default::default() + /// } + /// + /// #[rstest] + /// #[case(0)] // _case.description == case_1 + /// #[case::foo(0)] // _case.description == foo + /// fn test(_case: TestCase, #[case] num: u32) { + /// // Snapshot file name will have suffix of `@{_case.description}`, e.g. `@case_1` + /// assert_snapshot!(num); + /// } + /// } + /// ``` + /// + /// ## [TestCase] with manually set description + /// ```rust + /// #[cfg(test)] + /// mod tests { + /// use insta::assert_snapshot; + /// use roadster::testing::snapshot::{TestCase, TestCaseConfig}; + /// + /// #[test] + /// fn test() { + /// let _case = TestCaseConfig::builder().description("Custom description").build(); + /// // Snapshot file name will have suffix of `@Custom description` + /// assert_snapshot!("snapshot_value"); + /// } + /// } + /// ``` + #[builder(default, setter(strip_option, into))] + pub description: Option, + + /// Whether to set the `description` as the suffix of the snapshot file. + /// + /// It is particularly useful to set this to `true` when using `insta` together with `rstest`. + /// See: + #[builder(default = true)] + pub set_suffix: bool, + + /// Whether to redact UUIDs from snapshots. This is useful for tests involving + /// dynamically created UUIDs that will be different on every test run, or involve real UUIDs + /// that you don't want leaked in your source code. + #[builder(default = true)] + pub redact_uuid: bool, + + /// Whether to automatically bind the [Settings] to the current scope. If `true`, the settings + /// will be automatically applied for the test in which the [TestCase] was built. If `false`, + /// the settings will only be applied after manually calling [Settings::bind_to_scope], or + /// placing all relevant snapshot assertions inside a [Settings::bind] call. + /// + /// # Examples + /// + /// ## Auto bind to scope + /// ```rust + /// #[cfg(test)] + /// mod tests { + /// use insta::assert_snapshot; + /// use roadster::testing::snapshot::{TestCase, TestCaseConfig}; + /// + /// #[test] + /// fn test() { + /// let _case = TestCaseConfig::builder().description("Custom description").build(); + /// // Snapshot file name will have suffix of `@Custom description` + /// assert_snapshot!("snapshot_value"); + /// } + /// } + /// ``` + /// + /// ## Manually bind [Settings] scope + /// ```rust + /// #[cfg(test)] + /// mod tests { + /// use insta::assert_snapshot; + /// use roadster::testing::snapshot::{TestCase, TestCaseConfig}; + /// + /// #[test] + /// fn test() { + /// let case = TestCaseConfig::builder().bind_scope(false).build(); + /// // This snapshot will not have a suffix + /// assert_snapshot!("snapshot_value"); + /// + /// case.settings.bind(|| { + /// // This snapshot will have suffix `@test` (extracted from the curren thread name) + /// assert_snapshot!("snapshot_value_2"); + /// }); + /// } + /// } + /// ``` + #[builder(default = true)] + pub bind_scope: bool, +} + +/// Container for common `insta` snapshot [Settings] after they have been applied per the +/// [TestCaseConfig]. +#[non_exhaustive] +pub struct TestCase { + /// The description of the current test case. Either manually provided via the + /// [TestCaseConfigBuilder::description], or extracted from the current thread name. + pub description: String, + /// The `insta` [Settings] that are configured. + pub settings: Settings, + _settings_guard: Option, +} + +impl TestCase { + pub fn new() -> Self { + TestCaseConfig::builder().build() + } +} + +impl Default for TestCase { + fn default() -> Self { + TestCase::new() + } +} + +impl From for TestCase { + fn from(value: TestCaseConfig) -> Self { + let mut settings = value.settings.unwrap_or(Settings::clone_current()); + + let description = value + .description + .unwrap_or(description_from_current_thread()); + + if value.set_suffix { + snapshot_set_suffix(&mut settings, &description); + } + if value.redact_uuid { + snapshot_redact_uuid(&mut settings); + } + + let _settings_guard = if value.bind_scope { + Some(settings.bind_to_scope()) + } else { + None + }; + + Self { + description, + settings, + _settings_guard, + } + } +} + +/// Set the snapshot suffix on the [Settings]. +/// +/// Useful for using `insta` together with `rstest`. +/// See: +pub fn snapshot_set_suffix<'a>(settings: &'a mut Settings, suffix: &str) -> &'a mut Settings { + settings.set_snapshot_suffix(suffix); + settings +} + +/// Redact instances of UUIDs in snapshots. Applies a filter on the [Settings] to replace +/// sub-strings matching [UUID_REGEX] with `[uuid]`. +pub fn snapshot_redact_uuid(settings: &mut Settings) -> &mut Settings { + settings.add_filter(UUID_REGEX, "[uuid]"); + settings +} + +/// Extract the last segment of the current thread name to use as the test case description. +/// +/// See: +/// See: +fn description_from_current_thread() -> String { + let thread_name = current().name().unwrap_or("").to_string(); + let description = thread_name + .split("::") + .map(|item| item.split('_').skip(2).collect::>().join("_")) + .last() + .filter(|s| !s.is_empty()) + .unwrap_or(thread_name.split("::").last().unwrap().to_string()); + description +} + +#[cfg(test)] +mod tests { + use super::*; + use insta::assert_snapshot; + use rstest::rstest; + use uuid::Uuid; + + #[rstest] + #[case(0, false)] + #[case::rstest_description(1, false)] + #[case(2, true)] + #[cfg_attr(coverage_nightly, coverage(off))] + fn description(#[case] num: u32, #[case] manual_description: bool) { + let _case = if manual_description { + TestCaseConfig::builder() + .description("manual_description") + .build() + } else { + TestCase::new() + }; + + assert_snapshot!(num); + } + + #[rstest] + #[case(0, false, false)] + #[case(1, true, false)] + #[case(2, false, true)] + #[cfg_attr(coverage_nightly, coverage(off))] + fn bind_scope( + #[case] num: u32, + #[case] auto_bind_scope: bool, + #[case] manual_bind_scope: bool, + ) { + assert!(!(auto_bind_scope && manual_bind_scope)); + + let case = TestCaseConfig::builder() + .bind_scope(auto_bind_scope) + .build(); + + if manual_bind_scope { + case.settings.bind(|| { + assert_snapshot!(num); + }) + } else { + assert_snapshot!(num); + } + } + + #[test] + fn uuid() { + let _case = TestCase::new(); + + let uuid = Uuid::new_v4(); + + assert_snapshot!(format!("Foo '{uuid}' bar")); + } +} diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope.snap new file mode 100644 index 00000000..a9fd2a24 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +0 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_2.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_2.snap new file mode 100644 index 00000000..c9c1c8e6 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_2.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +1 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_3.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_3.snap new file mode 100644 index 00000000..51d16a46 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__bind_scope@case_3.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +2 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__description@case_1.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__description@case_1.snap new file mode 100644 index 00000000..a9fd2a24 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__description@case_1.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +0 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__description@manual_description.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__description@manual_description.snap new file mode 100644 index 00000000..51d16a46 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__description@manual_description.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +2 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__description@rstest_description.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__description@rstest_description.snap new file mode 100644 index 00000000..c9c1c8e6 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__description@rstest_description.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: num +--- +1 diff --git a/src/testing/snapshots/roadster__testing__snapshot__tests__uuid@uuid.snap b/src/testing/snapshots/roadster__testing__snapshot__tests__uuid@uuid.snap new file mode 100644 index 00000000..b088c647 --- /dev/null +++ b/src/testing/snapshots/roadster__testing__snapshot__tests__uuid@uuid.snap @@ -0,0 +1,5 @@ +--- +source: src/testing/snapshot.rs +expression: "format!(\"Foo '{uuid}' bar\")" +--- +Foo '[uuid]' bar diff --git a/src/util/mod.rs b/src/util/mod.rs index 00c0dc8b..ad962329 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,8 @@ -pub mod serde_util; -#[cfg(test)] -pub mod test_util; +pub mod regex; +pub mod serde; + +#[deprecated( + since = "0.5.6", + note = "The `serde_util` module was renamed to `serde`" +)] +pub use serde as serde_util; diff --git a/src/util/regex.rs b/src/util/regex.rs new file mode 100644 index 00000000..6b3eba4f --- /dev/null +++ b/src/util/regex.rs @@ -0,0 +1 @@ +pub const UUID_REGEX: &str = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; diff --git a/src/util/serde_util.rs b/src/util/serde.rs similarity index 100% rename from src/util/serde_util.rs rename to src/util/serde.rs diff --git a/src/util/test_util.rs b/src/util/test_util.rs deleted file mode 100644 index 3e5c8a59..00000000 --- a/src/util/test_util.rs +++ /dev/null @@ -1,72 +0,0 @@ -use insta::internals::SettingsBindDropGuard; -use std::thread::current; - -/// Set the `insta` snapshot suffix. Useful for using `insta` together with `rstest`. -/// -/// See: https://insta.rs/docs/patterns/ -#[cfg_attr(coverage_nightly, coverage(off))] -pub fn set_snapshot_suffix(suffix: &str) -> SettingsBindDropGuard { - let mut settings = insta::Settings::clone_current(); - settings.set_snapshot_suffix(suffix); - settings.bind_to_scope() -} - -/// Metadata for an `rstest` testcase. Useful for using `insta` together with `rstest`. -/// Automatically gets the description of the test case and sets it as the snaphot suffix. -/// -/// # Examples -/// -/// ```rust -/// -/// #[cfg(test)] -/// mod tests { -/// use insta::assert_snapshot; -/// use rstest::{fixture, rstest}; -/// use roadster::util::test_util::TestCase; -/// -/// #[fixture] -/// fn test_case() -> TestCase { -/// Default::default() -/// } -/// -/// #[rstest] -/// #[case(0)] // _test_case.description == case_1 -/// #[case::foo(0)] // _test_case.description == foo -/// fn test(_test_case: TestCase, #[case] num: u32) { -/// assert_snapshot!(num); -/// } -/// } -/// ``` -pub struct TestCase { - pub description: String, - _settings_guard: SettingsBindDropGuard, -} - -impl TestCase { - pub fn new() -> Self { - test_case() - } -} - -impl Default for TestCase { - fn default() -> Self { - TestCase::new() - } -} - -/// See: https://github.com/adriangb/pgpq/blob/b0b0f8c77c862c0483d81571e76f3a2b746136fc/pgpq/src/lib.rs#L649-L669 -/// See: https://github.com/la10736/rstest/issues/177 -#[cfg_attr(coverage_nightly, coverage(off))] -fn test_case() -> TestCase { - let name = current().name().unwrap().to_string(); - let description = name - .split("::") - .map(|item| item.split('_').skip(2).collect::>().join("_")) - .last() - .filter(|s| !s.is_empty()) - .unwrap_or(name.split("::").last().unwrap().to_string()); - TestCase { - _settings_guard: set_snapshot_suffix(&description), - description, - } -}