Skip to content

Commit

Permalink
Add tests for sidekiq builder and roadster cli
Browse files Browse the repository at this point in the history
Integrate with `insta` to help with asserting the parsed values of the
roadster cli.
  • Loading branch information
spencewenski committed May 21, 2024
1 parent 5fcb038 commit 7e9f0fb
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
[test-groups]
# Mocks of static methods need to run sequentially. This test group is for tests that mock AppService's static methods.
app-service-static-mock = { max-threads = 1 }
cli-static-mock = { max-threads = 1 }

[[profile.default.overrides]]
filter = 'test(#service::registry::tests::*) | test(#service::tests::*)'
test-group = "app-service-static-mock"

[[profile.default.overrides]]
filter = 'test(#cli::tests::*)'
test-group = "cli-static-mock"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Cargo.lock
# Code coverage
lcov.info

# Unreviewed snapshots
*.snap.new

# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ validator = { version = "0.18.1", features = ["derive"] }

[dev-dependencies]
cargo-husky = { version = "1.5.0", default-features = false, features = ["user-hooks"] }
insta = { version = "1.39.0", features = ["toml"] }
mockall = "0.12.1"
rstest = "0.19.0"

Expand Down
4 changes: 3 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use sea_orm::ConnectOptions;
use sea_orm_migration::MigrationTrait;
#[cfg(feature = "db-sql")]
use sea_orm_migration::MigratorTrait;
#[cfg(feature = "cli")]
use std::env;
use std::future;
use tracing::{instrument, warn};

Expand All @@ -30,7 +32,7 @@ where
A: App + Default + Send + Sync + 'static,
{
#[cfg(feature = "cli")]
let (roadster_cli, app_cli) = parse_cli::<A>()?;
let (roadster_cli, app_cli) = parse_cli::<A, _, _>(env::args_os())?;

#[cfg(feature = "cli")]
let environment = roadster_cli.environment.clone();
Expand Down
63 changes: 61 additions & 2 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::app_context::AppContext;
use crate::cli::roadster::{RoadsterCli, RunRoadsterCommand};
use async_trait::async_trait;
use clap::{Args, Command, FromArgMatches};
use std::ffi::OsString;

pub mod roadster;

Expand All @@ -32,9 +33,11 @@ where
) -> anyhow::Result<bool>;
}

pub(crate) fn parse_cli<A>() -> anyhow::Result<(RoadsterCli, A::Cli)>
pub(crate) fn parse_cli<A, I, T>(args: I) -> anyhow::Result<(RoadsterCli, A::Cli)>
where
A: App,
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
// Build the CLI by augmenting a default Command with both the roadster and app-specific CLIs
let cli = Command::default();
Expand Down Expand Up @@ -69,7 +72,7 @@ where
cli
};
// Build each CLI from the CLI args
let matches = cli.get_matches();
let matches = cli.get_matches_from(args);
let roadster_cli = RoadsterCli::from_arg_matches(&matches)?;
let app_cli = A::Cli::from_arg_matches(&matches)?;
Ok((roadster_cli, app_cli))
Expand Down Expand Up @@ -117,3 +120,59 @@ mockall::mock! {
fn augment_args_for_update(cmd: clap::Command) -> clap::Command;
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::util::test_util::TestCase;
use insta::assert_toml_snapshot;
use itertools::Itertools;
use rstest::{fixture, rstest};

#[fixture]
fn case() -> TestCase {
TestCase::default()
}

#[rstest]
#[case(None, None)]
#[case(Some("--environment test"), None)]
#[case(Some("--skip-validate-config"), None)]
#[case(Some("--allow-dangerous"), None)]
#[cfg_attr(
feature = "open-api",
case::list_routes(Some("roadster list-routes"), None)
)]
#[cfg_attr(feature = "open-api", case::list_routes(Some("r list-routes"), None))]
#[cfg_attr(feature = "open-api", case::open_api(Some("r open-api"), None))]
#[cfg_attr(feature = "db-sql", case::migrate(Some("r migrate up"), None))]
#[cfg_attr(coverage_nightly, coverage(off))]
fn parse_cli(_case: TestCase, #[case] args: Option<&str>, #[case] arg_list: Option<Vec<&str>>) {
// Arrange
let augment_args_context = MockCli::augment_args_context();
augment_args_context.expect().returning(|c| c);
let from_arg_matches_context = MockCli::from_arg_matches_context();
from_arg_matches_context
.expect()
.returning(|_| Ok(MockCli::default()));

let args = if let Some(args) = args {
args.split(' ').collect_vec()
} else if let Some(args) = arg_list {
args
} else {
Default::default()
};
// The first word is interpreted as the binary name
let args = vec!["binary_name"]
.into_iter()
.chain(args.into_iter())
.collect_vec();

// Act
let (roadster_cli, _a) = super::parse_cli::<MockTestApp, _, _>(args).unwrap();

// Assert
assert_toml_snapshot!(roadster_cli);
}
}
3 changes: 2 additions & 1 deletion src/cli/roadster/list_routes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Parser;
use serde_derive::Serialize;

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct ListRoutesArgs {}
10 changes: 6 additions & 4 deletions src/cli/roadster/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use anyhow::bail;
use async_trait::async_trait;
use clap::{Parser, Subcommand};
use sea_orm_migration::MigratorTrait;
use serde_derive::Serialize;
use tracing::warn;

use crate::app::App;
#[mockall_double::double]
use crate::app_context::AppContext;
use crate::cli::roadster::{RoadsterCli, RunRoadsterCommand};

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct MigrateArgs {
#[clap(subcommand)]
pub command: MigrateCommand,
Expand All @@ -30,7 +31,8 @@ where
}
}

#[derive(Debug, Subcommand)]
#[derive(Debug, Subcommand, Serialize)]
#[serde(tag = "type")]
pub enum MigrateCommand {
/// Apply pending migrations
Up(UpArgs),
Expand Down Expand Up @@ -78,14 +80,14 @@ where
}
}

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct UpArgs {
/// The number of pending migration steps to apply.
#[clap(short = 'n', long)]
pub steps: Option<u32>,
}

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct DownArgs {
/// The number of applied migration steps to rollback.
#[clap(short = 'n', long)]
Expand Down
11 changes: 7 additions & 4 deletions src/cli/roadster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::cli::roadster::print_config::PrintConfigArgs;
use crate::config::environment::Environment;
use async_trait::async_trait;
use clap::{Parser, Subcommand};
use serde_derive::Serialize;

#[cfg(feature = "open-api")]
pub mod list_routes;
Expand Down Expand Up @@ -38,7 +39,7 @@ where

/// Roadster: The Roadster CLI provides various utilities for managing your application. If no subcommand
/// is matched, Roadster will default to running/serving your application.
#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
#[command(version, about)]
pub struct RoadsterCli {
/// Specify the environment to use to run the application. This overrides the corresponding
Expand Down Expand Up @@ -86,7 +87,8 @@ where
}
}

#[derive(Debug, Subcommand)]
#[derive(Debug, Subcommand, Serialize)]
#[serde(tag = "type")]
pub enum RoadsterCommand {
/// Roadster subcommands. Subcommands provided by Roadster are listed under this subcommand in
/// order to avoid naming conflicts with the consumer's subcommands.
Expand All @@ -111,7 +113,7 @@ where
}
}

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct RoadsterArgs {
#[command(subcommand)]
pub command: RoadsterSubCommand,
Expand Down Expand Up @@ -163,7 +165,8 @@ where
}
}

#[derive(Debug, Subcommand)]
#[derive(Debug, Subcommand, Serialize)]
#[serde(tag = "type")]
pub enum RoadsterSubCommand {
/// List the API routes available in the app. Note: only the routes defined
/// using the `Aide` crate will be included in the output.
Expand Down
3 changes: 2 additions & 1 deletion src/cli/roadster/open_api_schema.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use clap::Parser;
use serde_derive::Serialize;
use std::path::PathBuf;

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct OpenApiArgs {
/// The file to write the schema to. If not provided, will write to stdout.
#[clap(short, long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
Expand Down
4 changes: 2 additions & 2 deletions src/cli/roadster/print_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::app::App;
use crate::app_context::AppContext;
use crate::cli::roadster::{RoadsterCli, RunRoadsterCommand};

#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
pub struct PrintConfigArgs {
/// Print the config with the specified format.
#[clap(short, long, default_value = "debug")]
Expand All @@ -19,7 +19,7 @@ pub struct PrintConfigArgs {
#[derive(
Debug, Clone, Eq, PartialEq, Serialize, Deserialize, EnumString, IntoStaticStr, clap::ValueEnum,
)]
#[serde(rename_all = "kebab-case")]
#[serde(rename_all = "kebab-case", tag = "type")]
#[strum(serialize_all = "kebab-case")]
pub enum Format {
Debug,
Expand Down
6 changes: 6 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@case_1.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = false
allow_dangerous = false
7 changes: 7 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@case_2.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
environment = 'test'
skip_validate_config = false
allow_dangerous = false
6 changes: 6 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@case_3.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = true
allow_dangerous = false
6 changes: 6 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@case_4.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = false
allow_dangerous = true
12 changes: 12 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@list_routes.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = false
allow_dangerous = false

[command]
type = 'Roadster'

[command.command]
type = 'ListRoutes'
15 changes: 15 additions & 0 deletions src/cli/snapshots/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = false
allow_dangerous = false

[command]
type = 'Roadster'

[command.command]
type = 'Migrate'

[command.command.command]
type = 'Up'
13 changes: 13 additions & 0 deletions src/cli/snapshots/roadster__cli__tests__parse_cli@open_api.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: src/cli/mod.rs
expression: roadster_cli
---
skip_validate_config = false
allow_dangerous = false

[command]
type = 'Roadster'

[command.command]
type = 'OpenApi'
pretty_print = false
31 changes: 31 additions & 0 deletions src/service/worker/sidekiq/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,37 @@ mod tests {
}
}

#[rstest]
#[case(true, true)]
#[case(false, false)]
#[tokio::test]
async fn clean_up_periodic_jobs_already_registered(
#[case] enabled: bool,
#[case] expect_err: bool,
) {
// Arrange
let register_count = if enabled { 1 } else { 0 };
let builder = setup(enabled, 0, register_count).await;
let builder = if enabled {
builder
.register_periodic_app_worker(
periodic::builder("* * * * * *").unwrap().name("foo"),
MockTestAppWorker::default(),
(),
)
.await
.unwrap()
} else {
builder
};

// Act
let result = builder.clean_up_periodic_jobs().await;

// Assert
assert_eq!(result.is_err(), expect_err);
}

#[rstest]
#[case(false, Default::default(), Default::default(), Default::default())]
#[case(true, Default::default(), Default::default(), Default::default())]
Expand Down
6 changes: 3 additions & 3 deletions src/service/worker/sidekiq/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,17 @@ mod tests {
#[case(true, None, 1, vec!["foo".to_string()], false, false)]
#[tokio::test]
#[cfg_attr(coverage_nightly, coverage(off))]
async fn foo(
async fn enabled(
#[case] default_enabled: bool,
#[case] enabled: Option<bool>,
#[case] sidekiq_enabled: Option<bool>,
#[case] num_workers: u32,
#[case] queues: Vec<String>,
#[case] has_redis_fetch: bool,
#[case] expected_enabled: bool,
) {
let mut config = AppConfig::empty(None).unwrap();
config.service.default_enable = default_enabled;
config.service.sidekiq.common.enable = enabled;
config.service.sidekiq.common.enable = sidekiq_enabled;
config.service.sidekiq.custom.num_workers = num_workers;
config.service.sidekiq.custom.queues = queues;

Expand Down
2 changes: 2 additions & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub mod serde_util;
#[cfg(test)]
pub mod test_util;
Loading

0 comments on commit 7e9f0fb

Please sign in to comment.