use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn write_fake_workspace(root: &Path) {
fs::create_dir_all(root.join("crates/features/src")).unwrap();
fs::create_dir_all(root.join("apps")).unwrap();
fs::write(
root.join("Cargo.toml"),
r#"
[workspace]
members = ["crates/*", "apps/*"]
[workspace.package]
version = "0.1.0"
"#,
)
.unwrap();
fs::write(
root.join("crates/features/src/lib.rs"),
"pub mod users;\n\npub use users::UsersModule;\n",
)
.unwrap();
fs::write(
root.join("crates/features/Cargo.toml"),
"[package]\nname = \"features\"\n\n[dependencies]\nnest-rs-core.workspace = true\n",
)
.unwrap();
fs::write(root.join(".env"), "NESTRS_DATABASE__URL=postgres://x\n").unwrap();
}
fn write_fake_app(root: &Path, name: &str) -> PathBuf {
let app = root.join("apps").join(name);
fs::create_dir_all(app.join("src")).unwrap();
fs::write(
app.join("Cargo.toml"),
format!("[package]\nname = \"{name}\"\n\n[dependencies]\nnest-rs-core.workspace = true\n"),
)
.unwrap();
fs::write(
app.join("src/module.rs"),
"use nest_rs_core::module;\nuse nest_rs_http::{HttpConfig, HttpModule};\n\n\
#[module(\n imports = [\n \
HttpModule::for_root(HttpConfig { port: 3000, ..Default::default() }),\n ],\n)]\n\
pub struct AppModule;\n",
)
.unwrap();
app
}
fn run_ok(dir: &Path, args: &[&str]) -> String {
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(args)
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"args {args:?} stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout).into_owned()
}
#[test]
fn new_standalone_hello_template() {
let dir = tempfile::tempdir().unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "demo-api", "--standalone"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let app = dir.path().join("demo-api");
assert!(app.join("src/main.rs").is_file());
assert!(app.join("src/lib.rs").is_file());
assert!(app.join("src/controller.rs").is_file());
assert!(app.join("tests/integration/main.rs").is_file());
assert!(app.join("tests/e2e/main.rs").is_file());
assert!(app.join("Cargo.toml").is_file());
assert!(app.join("Dockerfile").is_file());
assert!(app.join(".dockerignore").is_file());
assert!(app.join("rust-toolchain.toml").is_file());
assert!(app.join(".env").is_file());
assert!(app.join(".env.development").is_file());
let dev_env = fs::read_to_string(app.join(".env.development")).unwrap();
assert!(dev_env.contains("NESTRS_LOG=debug"));
assert!(app.join(".env.example").is_file());
let main_rs = fs::read_to_string(app.join("src/main.rs")).unwrap();
assert!(!main_rs.contains("OpenTelemetry"));
assert!(main_rs.contains("Environment::init"));
let module_rs = fs::read_to_string(app.join("src/module.rs")).unwrap();
assert!(!module_rs.contains("OpenTelemetryModule"));
let cargo = fs::read_to_string(app.join("Cargo.toml")).unwrap();
assert!(cargo.contains("[workspace]"));
assert!(cargo.contains("nest-rs-guards"));
assert!(cargo.contains("nest-rs-interceptors"));
assert!(!cargo.contains("nest-rs-opentelemetry"));
assert!(app.join(".gitignore").is_file());
assert!(app.join("Justfile").is_file());
let justfile = fs::read_to_string(app.join("Justfile")).unwrap();
assert!(justfile.contains("build:"));
assert!(justfile.contains("cargo build --release"));
assert!(justfile.contains("mod test"));
assert!(!justfile.contains("mod db"));
assert!(!app.join("db.just").exists());
assert!(app.join("test.just").is_file());
let test_just = fs::read_to_string(app.join("test.just")).unwrap();
assert!(test_just.contains("unit:"));
assert!(test_just.contains("e2e:"));
assert!(test_just.contains("doc:"));
assert!(test_just.contains("cargo test --doc"));
}
#[test]
fn new_workspace_greenfield() {
let dir = tempfile::tempdir().unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "acme"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let root = dir.path().join("acme");
assert!(root.join("Cargo.toml").is_file());
assert!(
root.join("crates/features/src/hello/http/controller.rs")
.is_file()
);
assert!(root.join("apps/hello/src/module.rs").is_file());
assert!(!root.join("apps/hello/src/controller.rs").exists());
assert!(root.join("apps/hello/tests/integration/main.rs").is_file());
assert!(root.join("apps/hello/tests/e2e/main.rs").is_file());
let smoke = fs::read_to_string(root.join("apps/hello/tests/integration/main.rs")).unwrap();
assert!(
!smoke.contains("with_test_telemetry"),
"that builder method is behind an optional feature the scaffold does not enable"
);
assert!(root.join("crates/migrations/src/bin/migrate.rs").is_file());
assert!(root.join("crates/migrations/src/migrator.rs").is_file());
assert!(root.join("crates/seed/src/main.rs").is_file());
assert!(!root.join(".dockerignore").exists());
assert!(root.join("Justfile").is_file());
let justfile = fs::read_to_string(root.join("Justfile")).unwrap();
assert!(justfile.contains("dev app=\"hello\""));
assert!(!justfile.contains("build-all"));
assert!(justfile.contains(r#"if app == "--all""#));
assert!(justfile.contains("mod test"));
assert!(justfile.contains("mod db"));
let test_just = fs::read_to_string(root.join("test.just")).unwrap();
assert!(test_just.contains("unit:"));
assert!(test_just.contains("e2e:"));
assert!(test_just.contains("cargo test --workspace --doc"));
let db_just = fs::read_to_string(root.join("db.just")).unwrap();
assert!(db_just.contains("up:"));
assert!(db_just.contains("reset: fresh seed"));
let module = fs::read_to_string(root.join("apps/hello/src/module.rs")).unwrap();
assert!(module.contains("HelloHttpModule"));
assert!(module.contains("features::hello"));
assert!(module.contains("port: 3000"));
let env = fs::read_to_string(root.join(".env")).unwrap();
assert!(!env.contains("NESTRS_HTTP__PORT"));
let cargo = fs::read_to_string(root.join("Cargo.toml")).unwrap();
assert!(cargo.contains("members = [\"crates/*\", \"apps/*\"]"));
}
#[test]
fn new_app_inside_nestrs_workspace() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "demo-api"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let app = dir.path().join("apps/demo-api");
assert!(app.join("src/module.rs").is_file());
assert!(app.join("src/main.rs").is_file());
assert!(!app.join("src/controller.rs").exists());
let module = fs::read_to_string(app.join("src/module.rs")).unwrap();
assert!(module.contains("HttpConfig { port: 3000"));
assert!(!module.contains("for_root(None)"));
let feature = dir.path().join("crates/features/src/demo_api");
assert!(feature.join("service.rs").is_file());
let controller = fs::read_to_string(feature.join("http/controller.rs")).unwrap();
assert!(
controller.contains(r#"#[controller(path = "/")]"#) && controller.contains("#[public]"),
"the scaffolded app must mount a public GET /: {controller}"
);
assert!(
module.contains("DemoApiHttpModule") && module.contains("features::demo_api"),
"and the app must import it: {module}"
);
let lib = fs::read_to_string(dir.path().join("crates/features/src/lib.rs")).unwrap();
assert!(lib.contains("pub mod demo_api;"), "features lib.rs: {lib}");
assert!(lib.contains("pub mod users;"), "features lib.rs: {lib}");
assert!(app.join("tests/integration/main.rs").is_file());
}
#[test]
fn new_app_refuses_to_reuse_an_existing_feature_name() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
fs::create_dir_all(dir.path().join("crates/features/src/users")).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "users"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!output.status.success());
assert!(!dir.path().join("apps/users").exists());
}
#[test]
fn new_app_picks_next_http_port() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
fs::create_dir_all(dir.path().join("apps/auth/src")).unwrap();
fs::write(
dir.path().join("apps/auth/src/module.rs"),
"HttpModule::for_root(HttpConfig { port: 3001, ..Default::default() })",
)
.unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "blog"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let module = fs::read_to_string(dir.path().join("apps/blog/src/module.rs")).unwrap();
assert!(module.contains("HttpConfig { port: 3002"));
}
#[test]
fn new_app_inside_workspace_already_exists() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
fs::create_dir_all(dir.path().join("apps/blog")).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "blog"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("already exists"));
assert!(stderr.contains("blog"));
}
#[test]
fn new_workspace_app_scaffold() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["new", "blog", "-o"])
.arg(dir.path())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let app = dir.path().join("apps/blog");
assert!(app.join("src/module.rs").is_file());
assert!(app.join("src/lib.rs").is_file());
assert!(!app.join("src/controller.rs").exists());
assert!(dir.path().join(".env").is_file());
assert!(dir.path().join(".env.development").is_file());
assert!(dir.path().join("Justfile").is_file());
assert!(dir.path().join(".gitignore").is_file());
assert!(dir.path().join("compose.yml").is_file());
let env = fs::read_to_string(dir.path().join(".env")).unwrap();
assert!(!env.contains("NESTRS_HTTP__PORT"));
assert!(env.contains("NESTRS_DATABASE__URL=postgres://"));
let module = fs::read_to_string(app.join("src/module.rs")).unwrap();
assert!(module.contains("HttpConfig { port: 3000"));
assert!(!module.contains("OpenTelemetryModule"));
}
#[test]
fn generate_feature_creates_port_and_wires_lib() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&["g", "feature", "posts", "-p", dir.path().to_str().unwrap()],
);
let feature = dir.path().join("crates/features/src/posts");
assert!(feature.join("mod.rs").is_file());
assert!(feature.join("module.rs").is_file());
assert!(feature.join("service.rs").is_file());
assert!(!feature.join("http").exists());
let lib = fs::read_to_string(dir.path().join("crates/features/src/lib.rs")).unwrap();
assert!(lib.contains("pub mod posts;"));
}
#[test]
fn generate_resource_creates_crud_slice_and_deps() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&["g", "resource", "posts", "-p", dir.path().to_str().unwrap()],
);
let feature = dir.path().join("crates/features/src/posts");
assert!(feature.join("entity.rs").is_file());
assert!(feature.join("service.rs").is_file());
assert!(feature.join("http/controller.rs").is_file());
let entity = fs::read_to_string(feature.join("entity.rs")).unwrap();
assert!(entity.contains("#[expose(name = \"Post\""));
assert!(entity.contains("table_name = \"post\""));
let root_cargo = fs::read_to_string(dir.path().join("Cargo.toml")).unwrap();
assert!(root_cargo.contains("nest-rs-seaorm"));
assert!(root_cargo.contains("schemars"));
assert!(root_cargo.contains("nest-rs-authz"));
assert!(
root_cargo.contains("sea-orm = { version = \"2.0\""),
"the generated pin tracks the released sea-orm, not a release candidate: {root_cargo}"
);
let features_cargo = fs::read_to_string(dir.path().join("crates/features/Cargo.toml")).unwrap();
assert!(features_cargo.contains("nest-rs-seaorm"));
assert!(features_cargo.contains("schemars"));
assert!(features_cargo.contains("nest-rs-authz"));
let lib = fs::read_to_string(dir.path().join("crates/features/src/lib.rs")).unwrap();
assert!(lib.contains("pub mod posts;"));
}
#[test]
fn generate_resource_emits_the_guarded_form_and_bootstraps_auth() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&["g", "resource", "posts", "-p", dir.path().to_str().unwrap()],
);
let feature = dir.path().join("crates/features/src/posts");
let controller = fs::read_to_string(feature.join("http/controller.rs")).unwrap();
assert!(
controller.contains("#[use_guards(AuthnGuard, AuthzGuard)]"),
"a DB-backed controller serves nothing without an ability guard: {controller}"
);
assert!(
controller.contains("#[crud(") && controller.contains("entity = PostEntity"),
"the resource controller uses the #[crud] form: {controller}"
);
let module = fs::read_to_string(feature.join("http/module.rs")).unwrap();
assert!(
module.contains("AuthzHttpModule"),
"the http module imports AuthzHttpModule: {module}"
);
let src = dir.path().join("crates/features/src");
assert!(src.join("authn/strategy.rs").is_file());
assert!(src.join("authz/ability.rs").is_file());
assert!(src.join("authz/http/guard.rs").is_file());
assert!(src.join("identity/claims.rs").is_file());
let env = fs::read_to_string(dir.path().join(".env")).unwrap();
assert!(env.contains("NESTRS_AUTHN__SECRET"));
}
#[test]
fn generate_auth_scaffolds_the_adapter_once() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&["g", "auth", "-p", dir.path().to_str().unwrap()],
);
let src = dir.path().join("crates/features/src");
let ability = fs::read_to_string(src.join("authz/ability.rs")).unwrap();
assert!(ability.contains("impl AbilityFactory for AppAbility"));
assert!(
ability.contains("fn define(") && ability.contains("fn define_visitor("),
"the scaffolded policy carries both branches: {ability}"
);
let guard = fs::read_to_string(src.join("authz/http/guard.rs")).unwrap();
assert!(guard.contains("AbilityGuard<AppAbility>"));
let lib = fs::read_to_string(src.join("lib.rs")).unwrap();
assert!(lib.contains("pub mod authn;"));
assert!(lib.contains("pub mod authz;"));
assert!(lib.contains("pub use identity::{Claims, Role};"));
let second = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["g", "auth", "-p", dir.path().to_str().unwrap()])
.current_dir(dir.path())
.output()
.unwrap();
assert!(!second.status.success());
}
#[test]
fn generate_auth_wires_both_roots_into_the_app() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let app = write_fake_app(dir.path(), "api");
run_ok(&app, &["g", "auth"]);
let module = fs::read_to_string(app.join("src/module.rs")).unwrap();
for (use_path, ident) in [
("features::authn::AuthnModule", "AuthnModule"),
("features::authz::AuthzHttpModule", "AuthzHttpModule"),
] {
assert!(module.contains(&format!("use {use_path};")), "{module}");
assert!(
module.contains(&format!("{ident},")),
"the imports array must list {ident}: {module}"
);
}
assert!(module.contains("HttpModule::for_root"), "{module}");
}
#[test]
fn generate_resource_wires_the_auth_roots_it_bootstrapped() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let app = write_fake_app(dir.path(), "api");
let module_rs = app.join("src/module.rs");
let with_db = fs::read_to_string(&module_rs)
.unwrap()
.replace(" ],", " DatabaseModule::for_root(None),\n ],");
fs::write(&module_rs, with_db).unwrap();
run_ok(&app, &["g", "resource", "posts"]);
let module = fs::read_to_string(&module_rs).unwrap();
for ident in ["PostsHttpModule", "AuthnModule", "AuthzHttpModule"] {
assert!(module.contains(&format!("{ident},")), "{ident}: {module}");
}
}
fn write_fake_migrations_crate(root: &Path) {
let dir = root.join("crates/migrations/src");
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join("lib.rs"),
"mod m20260101_000000_init;\nmod migrator;\n\npub use migrator::Migrator;\n",
)
.unwrap();
fs::write(
dir.join("migrator.rs"),
"use sea_orm_migration::prelude::*;\n\nuse super::{\n m20260101_000000_init,\n};\n\npub struct Migrator;\n",
)
.unwrap();
fs::write(dir.join("m20260101_000000_init.rs"), "// init\n").unwrap();
}
#[test]
fn generate_migration_registers_in_both_lib_and_migrator() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
write_fake_migrations_crate(dir.path());
run_ok(
dir.path(),
&[
"g",
"migration",
"create_widget",
"-p",
dir.path().to_str().unwrap(),
],
);
let mig = dir.path().join("crates/migrations/src");
let generated: Vec<_> = fs::read_dir(&mig)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.ends_with("_create_widget.rs"))
.collect();
assert_eq!(
generated.len(),
1,
"one migration file created: {generated:?}"
);
let stem = generated[0].trim_end_matches(".rs").to_string();
let lib = fs::read_to_string(mig.join("lib.rs")).unwrap();
assert!(lib.contains(&format!("mod {stem};")), "lib.rs: {lib}");
let migrator = fs::read_to_string(mig.join("migrator.rs")).unwrap();
assert!(
migrator.matches(&stem).count() >= 2,
"migrator must import and box the new migration: {migrator}"
);
assert!(
migrator.contains(&format!("Box::new({stem}::Migration)")),
"migrator vec: {migrator}"
);
assert!(
migrator.contains("Box::new(m20260101_000000_init::Migration)"),
"migrator kept init: {migrator}"
);
assert!(
migrator.contains("pub async fn migrate"),
"migrator has migrate fn"
);
}
#[test]
fn generate_migration_bootstraps_the_crate_when_absent() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&[
"g",
"migration",
"create_widget",
"-p",
dir.path().to_str().unwrap(),
],
);
let mig = dir.path().join("crates/migrations/src");
assert!(mig.join("lib.rs").is_file());
assert!(mig.join("bin/migrate.rs").is_file());
assert!(dir.path().join("crates/seed/src/main.rs").is_file());
let lib = fs::read_to_string(mig.join("lib.rs")).unwrap();
assert!(
lib.contains("_create_widget;"),
"lib.rs registers it: {lib}"
);
let migrator = fs::read_to_string(mig.join("migrator.rs")).unwrap();
assert!(
migrator.contains("_create_widget::Migration)"),
"migrator boxes it: {migrator}"
);
}
#[test]
fn generate_http_adapter_wires_feature_mod() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let path = dir.path().to_str().unwrap();
run_ok(dir.path(), &["g", "feature", "posts", "-p", path]);
run_ok(dir.path(), &["g", "http", "posts", "-p", path]);
let feature = dir.path().join("crates/features/src/posts");
assert!(feature.join("http/controller.rs").is_file());
assert!(feature.join("http/module.rs").is_file());
let mod_rs = fs::read_to_string(feature.join("mod.rs")).unwrap();
assert!(mod_rs.contains("pub mod http;"));
assert!(mod_rs.contains("PostsController"));
assert!(mod_rs.contains("PostsHttpModule"));
}
#[test]
fn generate_ws_adapter_ensures_dep_and_wires() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let path = dir.path().to_str().unwrap();
run_ok(dir.path(), &["g", "feature", "posts", "-p", path]);
run_ok(dir.path(), &["g", "ws", "posts", "-p", path]);
assert!(
dir.path()
.join("crates/features/src/posts/ws/gateway.rs")
.is_file()
);
let features_cargo = fs::read_to_string(dir.path().join("crates/features/Cargo.toml")).unwrap();
assert!(features_cargo.contains("nest-rs-ws"));
}
#[test]
fn generate_queue_adapter_puts_command_at_the_port() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let path = dir.path().to_str().unwrap();
run_ok(dir.path(), &["g", "feature", "posts", "-p", path]);
run_ok(dir.path(), &["g", "queue", "posts", "-p", path]);
let feature = dir.path().join("crates/features/src/posts");
let command_rs = fs::read_to_string(feature.join("command.rs")).unwrap();
assert!(command_rs.contains("pub struct ProcessPostCommand"));
let processor_rs = fs::read_to_string(feature.join("queue/processor.rs")).unwrap();
assert!(processor_rs.contains("use crate::posts::ProcessPostCommand;"));
assert!(processor_rs.contains("job: ProcessPostCommand"));
assert!(!processor_rs.contains("pub struct ProcessPostCommand"));
let mod_rs = fs::read_to_string(feature.join("mod.rs")).unwrap();
assert!(mod_rs.contains("mod command;"));
assert!(mod_rs.contains("pub use command::ProcessPostCommand;"));
assert!(mod_rs.contains("pub mod queue;"));
assert!(mod_rs.contains("PostsQueueModule"));
}
#[test]
fn generate_adapter_is_rejected_on_rerun() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let path = dir.path().to_str().unwrap();
run_ok(dir.path(), &["g", "feature", "posts", "-p", path]);
run_ok(dir.path(), &["g", "http", "posts", "-p", path]);
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["g", "http", "posts", "-p", path])
.output()
.unwrap();
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("already exists"));
}
#[test]
fn generate_adapter_requires_existing_feature() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["g", "ws", "ghost", "-p", dir.path().to_str().unwrap()])
.output()
.unwrap();
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("not found"));
}
#[test]
fn generate_dry_run_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
write_fake_workspace(dir.path());
run_ok(
dir.path(),
&[
"g",
"feature",
"posts",
"--dry-run",
"-p",
dir.path().to_str().unwrap(),
],
);
assert!(!dir.path().join("crates/features/src/posts").exists());
let lib = fs::read_to_string(dir.path().join("crates/features/src/lib.rs")).unwrap();
assert!(!lib.contains("pub mod posts;"));
}
#[test]
fn version_prints_single_line() {
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.arg("version")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let line = stdout.trim();
assert!(line.starts_with("NestRS "));
assert!(!line.contains('\n'));
}
#[test]
fn about_prints_metadata_block() {
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.arg("about")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Version:"));
assert!(stdout.contains("Tagline:"));
assert!(stdout.contains("Yoann Vanitou"));
}
#[test]
fn run_subcommand_is_listed_in_help() {
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.arg("--help")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("run"));
}
#[test]
fn run_without_toolchain_and_no_bootstrap_errors_clearly() {
let empty = tempfile::tempdir().unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.args(["run", "--no-bootstrap", "dev"])
.env("PATH", empty.path())
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("just"), "stderr: {stderr}");
assert!(stderr.contains("cargo install"), "stderr: {stderr}");
}
#[test]
fn doctor_passes_with_rust_toolchain() {
let output = Command::new(env!("CARGO_BIN_EXE_nestrs"))
.arg("doctor")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}