diff --git a/Cargo.toml b/Cargo.toml index 5528cb1..509f766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "star-setup" -version = "0.4.7" +version = "0.4.8" edition = "2021" repository = "https://github.com/star-setup/core" description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems" diff --git a/README.md b/README.md index 187ac0d..f1493f5 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,10 @@ Clones multiple repositories into a single workspace and auto-detects the build # Clone and build a test repo and a manual repo list star-setup username/repo --repos user/lib1 user/lib2 -# Clone and build a test repo and a saved profile +# Clone and build a saved profile +star-setup --profile myprofile + +# Override a profile's test repo star-setup username/repo --profile myprofile ``` @@ -280,7 +283,10 @@ star-setup username/repo --config myconfig ### Profile Mode Profiles represent a saved ecosystem of libraries commonly used together. ```bash -# Add a profile +# Add a profile with a test repo and dependencies +star-setup profile add myprofile --test-repo user/app user/lib1 user/lib2 + +# Add a dependency-only profile star-setup profile add myprofile user/lib1 user/lib2 # List profiles @@ -290,7 +296,7 @@ star-setup profile list star-setup profile remove myprofile # Use a profile -star-setup username/repo --profile myprofile +star-setup --profile myprofile ``` ### Development diff --git a/src/cli/args.rs b/src/cli/args.rs index 20208cb..a52987e 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -3,7 +3,7 @@ use crate::{ resolve_with_config, BuildFlags, ConfigCommand, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ProfileCommand, ResolvedArgs, WorkspaceCommand, }, - config::SetupConfig, + config::Config, }; use clap::{Parser, Subcommand}; @@ -57,7 +57,7 @@ impl Args { /// Parses CLI arguments and resolves them against the provided `SetupConfig`. /// # Errors /// Returns an error if the named config does not exist in the loaded `SetupConfig`. - pub fn parse_with_config(config: &SetupConfig) -> Result { + pub fn parse_with_config(config: &Config) -> Result { resolve_with_config(Args::parse(), config) } } diff --git a/src/cli/commands.rs b/src/cli/commands.rs index 0036324..4bcef76 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -51,7 +51,10 @@ pub enum ProfileAction { Add { /// Name of the profile. name: String, - /// Repository list (username/repo ...). + /// Test repository (user/repo). + #[arg(long)] + test_repo: Option, + /// Repository list (user/lib1 user/lib2). repos: Vec, }, /// Remove a named profile. diff --git a/src/cli/resolve.rs b/src/cli/resolve.rs index 45b2553..67c925d 100644 --- a/src/cli/resolve.rs +++ b/src/cli/resolve.rs @@ -3,7 +3,7 @@ use crate::{ Args, BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags, }, - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, ctx::RunFlags, }; @@ -115,7 +115,7 @@ fn resolve_mono_flags(mono: MonoRepoFlags, default: Option<&ConfigEntry>) -> Res /// Resolves raw `Args` into `ResolvedArgs` by applying config defaults and CLI overrides. /// # Errors /// Returns an error if the named config does not exist in the provided `SetupConfig`. -pub fn resolve_with_config(args: Args, config: &SetupConfig) -> Result { +pub fn resolve_with_config(args: Args, config: &Config) -> Result { let config_name = args.config_name.as_deref().unwrap_or("default"); let default = config.configs.get(config_name); diff --git a/src/commands/handlers.rs b/src/commands/handlers.rs index d0941b8..21711b8 100644 --- a/src/commands/handlers.rs +++ b/src/commands/handlers.rs @@ -1,23 +1,21 @@ use crate::{ cli::{ - ConfigAction, ProfileAction, WorkspaceAction, - WorkspaceAction::{Clean, Status, Update}, - }, - config::{ - add_config, create_default_config, list_configs, remove_config, ConfigEntry, SetupConfig, + ConfigAction, ProfileAction, + WorkspaceAction::{self, Clean, Status, Update}, }, + config::{add_config, create_default_config, list_configs, remove_config, Config, ConfigEntry}, ctx::{with_runner, IoCtx, RunFlags}, - profile::{add_profile, list_profiles, remove_profile}, + profile::{add_profile, list_profiles, remove_profile, Profile}, workspace::resolve_workspace, }; -use std::{error::Error, iter::once, path::PathBuf}; +use std::{error::Error, path::PathBuf}; /// Handles configuration-related subcommands. /// # Errors /// Returns an error if configuration initializing, addition, or removal fails. pub fn handle_config_cmd( action: ConfigAction, - config: &mut SetupConfig, + config: &mut Config, config_path: PathBuf, yes: bool, io: &mut IoCtx, @@ -46,7 +44,7 @@ pub fn handle_config_cmd( /// Returns an error if adding or removing profiles encounters an I/O or validation failure. pub fn handle_profile_cmd( action: ProfileAction, - config: &mut SetupConfig, + config: &mut Config, yes: bool, io: &mut IoCtx, flags: RunFlags, @@ -54,9 +52,13 @@ pub fn handle_profile_cmd( match action { ProfileAction::List => list_profiles(config, io), ProfileAction::Remove { name } => remove_profile(config, &name, yes, io, flags)?, - ProfileAction::Add { name, repos } => { - let vals = once(name).chain(repos).collect::>(); - add_profile(config, &vals, yes, io, flags)?; + ProfileAction::Add { + name, + test_repo, + repos, + } => { + let profile = Profile::from_args(test_repo, repos)?; + add_profile(config, &name, &profile, yes, io, flags)?; } } Ok(()) diff --git a/src/commands/mono/mod.rs b/src/commands/mono/mod.rs index ed0aaa4..3f72cc7 100644 --- a/src/commands/mono/mod.rs +++ b/src/commands/mono/mod.rs @@ -7,7 +7,9 @@ pub use display::{print_setup_complete, resolve_setup_paths}; pub mod mode; pub use mode::mono_repo_mode; pub mod resolve; -pub use resolve::{resolve_repos_for_mono, resolve_test_repo}; +pub use resolve::{ + resolve_profile, resolve_repos_for_mono, resolve_test_repo, resolve_test_repo_for_mono, +}; pub mod wraps; pub use wraps::hoist_wraps; pub mod setup; diff --git a/src/commands/mono/mode.rs b/src/commands/mono/mode.rs index 2edf795..c67eeb1 100644 --- a/src/commands/mono/mode.rs +++ b/src/commands/mono/mode.rs @@ -1,14 +1,15 @@ use crate::{ cli::{detect_mono_build_system, BuildSystem::Npm, ResolvedArgs}, commands::{ - build_project, build_repo_list, extract_repo_input, maybe_open_dev_server, + build_project, build_repo_list, maybe_open_dev_server, mono::{ display::{resolve_setup_paths, SetupPaths}, generate_mono_config, generate_watch_scripts, open_watch_scripts, print_setup_complete, + resolve::{resolve_profile, resolve_test_repo_for_mono}, }, - prepare_build_dir, print_mode_header, resolve_repos_for_mono, resolve_test_repo, ModeHeader, + prepare_build_dir, print_mode_header, resolve_repos_for_mono, ModeHeader, }, - config::SetupConfig, + config::Config, ctx::RunCtx, repository::{clone_repos, repo_dir_name}, utils::{detect_or_dry_run, dry_run_or_do}, @@ -24,14 +25,14 @@ use std::{ /// Returns an error if no repository is specified, directory creation fails, or any build system command fails. pub fn mono_repo_mode( args: &ResolvedArgs, - config: &SetupConfig, + config: &Config, base_dir: &Path, ctx: &mut RunCtx<'_, '_>, ) -> Result<(), String> { let total = Instant::now(); - let repo_input = extract_repo_input(args)?; - let test_repo = resolve_test_repo(repo_input)?; - let deps = resolve_repos_for_mono(args, config, &mut ctx.io)?; + let profile = resolve_profile(args, config, &mut ctx.io)?; + let test_repo = resolve_test_repo_for_mono(args, profile)?; + let deps = resolve_repos_for_mono(args, profile); let repos = build_repo_list(&test_repo, &deps); print_mode_header( diff --git a/src/commands/mono/resolve.rs b/src/commands/mono/resolve.rs index ad1d9db..af3a62d 100644 --- a/src/commands/mono/resolve.rs +++ b/src/commands/mono/resolve.rs @@ -1,4 +1,9 @@ -use crate::{cli::ResolvedArgs, config::SetupConfig, ctx::IoCtx, profile::list_profiles}; +use crate::{ + cli::ResolvedArgs, + config::Config, + ctx::IoCtx, + profile::{list_profiles, Profile}, +}; /// Normalizes a repository input to `username/repo` format. /// # Errors @@ -24,26 +29,43 @@ pub fn resolve_test_repo(repo_input: &str) -> Result { } } -/// Resolves the list of repositories for mono-repo mode from a profile or explicit repo list. +/// Resolves the named profile for mono-repo mode, if one was requested. /// # Errors -/// Returns an error if the specified profile does not exist, or has no repositories. -pub fn resolve_repos_for_mono( +/// Returns an error if `--profile` names a profile that doesn't exist. +pub fn resolve_profile<'a>( args: &ResolvedArgs, - config: &SetupConfig, + config: &'a Config, io: &mut IoCtx<'_>, -) -> Result, String> { - if let Some(profile_name) = &args.mono.profile { - let profile_repos = config.profiles.get(profile_name).ok_or_else(|| { +) -> Result, String> { + match &args.mono.profile { + Some(name) => config.profiles.get(name).map(Some).ok_or_else(|| { list_profiles(config, io); - format!("Profile '{profile_name}' not found") - })?; - if profile_repos.is_empty() { - return Err(format!("Profile '{profile_name}' has no repositories")); - } - Ok(profile_repos.clone()) - } else if let Some(r) = &args.mono.repos { - Ok(r.clone()) - } else { - Err("No repos or profile specified for mono-repo mode".to_string()) + format!("Profile '{name}' not found") + }), + None => Ok(None), } } + +/// Resolves the test repo for mono-repo mode: CLI positional wins, else the profile's. +/// # Errors +/// Returns an error if neither a positional repo nor a profile test repo is available. +pub fn resolve_test_repo_for_mono( + args: &ResolvedArgs, + profile: Option<&Profile>, +) -> Result { + match args.repo.as_deref() { + Some(r) => resolve_test_repo(r.trim_end_matches('/')), + None => profile + .and_then(|p| p.test_repo.clone()) + .ok_or_else(|| "No repository specified".to_string()), + } +} + +/// Resolves the dependency repositories for mono-repo mode from a profile or explicit list. +#[must_use] +pub fn resolve_repos_for_mono(args: &ResolvedArgs, profile: Option<&Profile>) -> Vec { + profile + .map(|p| p.deps.clone()) + .or_else(|| args.mono.repos.clone()) + .unwrap_or_default() +} diff --git a/src/config/crud.rs b/src/config/crud.rs index 1570091..47a6ebc 100644 --- a/src/config/crud.rs +++ b/src/config/crud.rs @@ -1,5 +1,5 @@ use crate::{ - config::{format_entry, persist_or_dry_run, save_config, ConfigEntry, SetupConfig}, + config::{format_entry, persist_or_dry_run, save_config, Config, ConfigEntry}, ctx::{IoCtx, RunFlags}, prompts::confirm_abort, }; @@ -7,18 +7,18 @@ use dunce::canonicalize; use std::path::PathBuf; /// Inserts or overwrites a named configuration entry. -pub fn insert_config(config: &mut SetupConfig, name: &str, entry: ConfigEntry) { +pub fn insert_config(config: &mut Config, name: &str, entry: ConfigEntry) { config.configs.insert(name.to_string(), entry); } /// Removes a named configuration entry. Returns `true` if it existed. -pub fn remove_config_entry(config: &mut SetupConfig, name: &str) -> bool { +pub fn remove_config_entry(config: &mut Config, name: &str) -> bool { config.configs.remove(name).is_some() } /// Returns `true` if a configuration with the given name exists. #[must_use] -pub fn has_config(config: &SetupConfig, name: &str) -> bool { +pub fn has_config(config: &Config, name: &str) -> bool { config.configs.contains_key(name) } @@ -51,7 +51,7 @@ pub fn create_default_config( ) .ok(); } else { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); config .configs @@ -79,7 +79,7 @@ pub fn create_default_config( /// # Errors /// Returns an error if saving the config file fails. pub fn add_config( - config: &mut SetupConfig, + config: &mut Config, name: &str, entry: ConfigEntry, yes: bool, @@ -120,7 +120,7 @@ pub fn add_config( /// # Errors /// Returns an error if saving the config file fails. pub fn remove_config( - config: &mut SetupConfig, + config: &mut Config, name: &str, yes: bool, io: &mut IoCtx<'_>, diff --git a/src/config/display.rs b/src/config/display.rs index 7d0e376..c66d794 100644 --- a/src/config/display.rs +++ b/src/config/display.rs @@ -1,5 +1,5 @@ use crate::{ - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, ctx::IoCtx, }; use std::fmt::Write as FmtWrite; @@ -44,7 +44,7 @@ pub fn format_entry(e: &ConfigEntry) -> String { } /// Lists all saved configuration entries. -pub fn list_configs(config: &SetupConfig, io: &mut IoCtx<'_>) { +pub fn list_configs(config: &Config, io: &mut IoCtx<'_>) { if config.configs.is_empty() { writeln!(io.output, " No configurations created.").ok(); writeln!( diff --git a/src/config/io.rs b/src/config/io.rs index 534062f..3f8faac 100644 --- a/src/config/io.rs +++ b/src/config/io.rs @@ -1,5 +1,5 @@ use crate::{ - config::SetupConfig, + config::Config, ctx::{IoCtx, RunFlags}, }; use serde_json::{from_str, to_string_pretty}; @@ -37,7 +37,7 @@ pub fn load_config( verbose: bool, timing: bool, output: &mut impl Write, -) -> SetupConfig { +) -> Config { if verbose { writeln!(output, "Loading config").ok(); } @@ -53,7 +53,7 @@ pub fn load_config( } let result = crate::time!(timing, output, "Read config", { match read_to_string(path) { - Ok(contents) => match from_str::(&contents) { + Ok(contents) => match from_str::(&contents) { Ok(mut config) => { config.path = Some(path.clone()); if verbose { @@ -93,14 +93,14 @@ pub fn load_config( if verbose || timing { writeln!(output).ok(); } - SetupConfig::new() + Config::new() } /// Serializes the configuration and writes it to the path stored in `config.path`. /// # Errors /// Returns an error if serialization fails or if the file cannot be written. pub fn save_config( - config: &mut SetupConfig, + config: &mut Config, timing: bool, output: &mut impl Write, ) -> Result { @@ -128,12 +128,12 @@ pub fn save_config( /// # Errors /// Returns an error if saving the config file fails. pub fn persist_or_dry_run( - config: &mut SetupConfig, + config: &mut Config, flags: RunFlags, io: &mut IoCtx<'_>, would_msg: &str, - mutate: impl FnOnce(&mut SetupConfig), - on_saved: impl FnOnce(&SetupConfig, &Path, &mut IoCtx<'_>), + mutate: impl FnOnce(&mut Config), + on_saved: impl FnOnce(&Config, &Path, &mut IoCtx<'_>), ) -> Result<(), String> { if flags.dry_run { writeln!(io.output, " {would_msg}").ok(); diff --git a/src/config/mod.rs b/src/config/mod.rs index b74b6a5..ca7cc2d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,4 +7,4 @@ pub use display::{format_entry, list_configs}; pub mod io; pub use io::{config_locations, load_config, persist_or_dry_run, save_config}; pub mod types; -pub use types::{ConfigEntry, SetupConfig}; +pub use types::{Config, ConfigEntry}; diff --git a/src/config/types.rs b/src/config/types.rs index e3a6057..decc891 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1,9 +1,32 @@ -use crate::cli::{ - BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, +use crate::{ + cli::{BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs}, + profile::Profile, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; +/// Top-level configuration structure. +#[derive(Serialize, Deserialize, Default)] +pub struct Config { + /// Named configuration entries. + #[serde(default)] + pub configs: HashMap, + /// Named profile entries mapping profile names to repository lists. + #[serde(default)] + pub profiles: HashMap, + /// Path to the config file this was loaded from, if any. + #[serde(skip)] + pub path: Option, +} + +impl Config { + /// Creates a new empty `Config`. + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + /// Represents a single named configuration entry. #[allow(clippy::struct_excessive_bools)] #[derive(Serialize, Deserialize)] @@ -124,25 +147,3 @@ impl From<&ResolvedArgs> for ConfigEntry { } } } - -/// Top-level configuration structure. -#[derive(Serialize, Deserialize, Default)] -pub struct SetupConfig { - /// Named configuration entries. - #[serde(default)] - pub configs: HashMap, - /// Named profile entries mapping profile names to repository lists. - #[serde(default)] - pub profiles: HashMap>, - /// Path to the config file this was loaded from, if any. - #[serde(skip)] - pub path: Option, -} - -impl SetupConfig { - /// Creates a new empty `SetupConfig`. - #[must_use] - pub fn new() -> Self { - Self::default() - } -} diff --git a/src/profile/crud.rs b/src/profile/crud.rs index bbfefd5..9aaad5e 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -1,45 +1,38 @@ use crate::{ - config::{persist_or_dry_run, SetupConfig}, + config::{persist_or_dry_run, Config}, ctx::{IoCtx, RunFlags}, - profile::print_profile_details, + profile::{print_profile_details, Profile}, prompts::confirm_abort, }; /// Inserts or overwrites a named profile. -pub fn insert_profile(config: &mut SetupConfig, name: &str, repos: Vec) { - config.profiles.insert(name.to_string(), repos); +pub fn insert_profile(config: &mut Config, name: &str, profile: Profile) { + config.profiles.insert(name.to_string(), profile); } /// Removes a named profile. Returns `true` if it existed. -pub fn remove_profile_entry(config: &mut SetupConfig, name: &str) -> bool { +pub fn remove_profile_entry(config: &mut Config, name: &str) -> bool { config.profiles.remove(name).is_some() } /// Returns `true` if a profile with the given name exists. #[must_use] -pub fn has_profile(config: &SetupConfig, name: &str) -> bool { +pub fn has_profile(config: &Config, name: &str) -> bool { config.profiles.contains_key(name) } /// Adds a new profile to the configuration. -/// args: [name, repo1, repo2, ...] /// # Errors -/// Returns an error if fewer than two arguments are provided or if saving fails. +/// Returns an error if saving fails. pub fn add_profile( - config: &mut SetupConfig, - args: &[String], + config: &mut Config, + name: &str, + profile: &Profile, yes: bool, io: &mut IoCtx<'_>, flags: RunFlags, ) -> Result<(), String> { - if args.len() < 2 { - return Err("profile add requires NAME REPO1 [REPO2 ...]".to_string()); - } - - let name = args[0].clone(); - let repos = args[1..].to_vec(); - - if has_profile(config, &name) + if has_profile(config, name) && !confirm_abort( &format!(" Warning: Profile '{name}' already exists. Overwrite?"), yes, @@ -54,18 +47,14 @@ pub fn add_profile( flags, io, &format!("Would save profile '{name}' to config file"), - |config| insert_profile(config, &name, repos.clone()), + |config| insert_profile(config, name, profile.clone()), |_config, path, io| { writeln!(io.output, " Profile '{name}' added successfully").ok(); writeln!(io.output, " Configuration saved to: {}", path.display()).ok(); }, )?; - print_profile_details(io.output, "Profile details:", "Repositories", &repos); - writeln!( - io.output, - " Usage: star-setup username/test-repo --profile {name}" - ) - .ok(); + print_profile_details(io.output, "Profile details:", "Repositories", profile); + writeln!(io.output, " Usage: star-setup --profile {name}").ok(); Ok(()) } @@ -73,13 +62,13 @@ pub fn add_profile( /// # Errors /// Returns an error if saving the config file fails. pub fn remove_profile( - config: &mut SetupConfig, + config: &mut Config, name: &str, yes: bool, io: &mut IoCtx<'_>, flags: RunFlags, ) -> Result<(), String> { - let repos = match config.profiles.get(name) { + let profile = match config.profiles.get(name) { None => { writeln!(io.output, " Warning: Profile '{name}' not found.").ok(); return Ok(()); @@ -91,7 +80,7 @@ pub fn remove_profile( io.output, &format!("Profile '{name}'"), "Repositories", - &repos, + &profile, ); if !confirm_abort( diff --git a/src/profile/display.rs b/src/profile/display.rs index 02231de..b2a3bc0 100644 --- a/src/profile/display.rs +++ b/src/profile/display.rs @@ -1,21 +1,27 @@ -use crate::{config::types::SetupConfig, ctx::IoCtx}; +use crate::{config::Config, ctx::IoCtx, profile::Profile}; use std::io::Write; pub fn print_profile_details( output: &mut (impl Write + ?Sized), title: &str, label: &str, - repos: &[String], + profile: &Profile, ) { + let Profile { test_repo, deps } = profile; writeln!(output, " {title}").ok(); - writeln!(output, " {label}: {}", repos.len()).ok(); - for repo in repos { - writeln!(output, " - {repo}").ok(); + if let Some(t) = test_repo { + writeln!(output, " {t}").ok(); + } + if !deps.is_empty() { + writeln!(output, " {label}: {}", deps.len()).ok(); + for d in deps { + writeln!(output, " - {d}").ok(); + } } } /// Lists all configured profiles. -pub fn list_profiles(config: &SetupConfig, io: &mut IoCtx<'_>) { +pub fn list_profiles(config: &Config, io: &mut IoCtx<'_>) { if config.profiles.is_empty() { writeln!( io.output, @@ -24,10 +30,9 @@ pub fn list_profiles(config: &SetupConfig, io: &mut IoCtx<'_>) { .ok(); return; } - writeln!(io.output, "Configured profiles:\n").ok(); - for (name, repos) in &config.profiles { - print_profile_details(io.output, name, "Repositories", repos); + for (name, profile) in &config.profiles { + print_profile_details(io.output, name, "Repositories", profile); writeln!(io.output).ok(); } } diff --git a/src/profile/mod.rs b/src/profile/mod.rs index ce2c008..493425e 100644 --- a/src/profile/mod.rs +++ b/src/profile/mod.rs @@ -2,3 +2,5 @@ pub mod crud; pub use crud::{add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry}; pub mod display; pub use display::{list_profiles, print_profile_details}; +pub mod types; +pub use types::Profile; diff --git a/src/profile/types.rs b/src/profile/types.rs new file mode 100644 index 0000000..9a0b47c --- /dev/null +++ b/src/profile/types.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct Profile { + pub test_repo: Option, + pub deps: Vec, +} + +impl Profile { + /// # Errors + /// Returns an error if neither a test repo nor any dependency is given. + pub fn from_args(test_repo: Option, deps: Vec) -> Result { + if test_repo.is_none() && deps.is_empty() { + return Err("profile requires --test-repo or at least one dependency repo".to_string()); + } + Ok(Self { test_repo, deps }) + } +} diff --git a/tests/cli/resolve.rs b/tests/cli/resolve.rs index 0db2577..7e8a74e 100644 --- a/tests/cli/resolve.rs +++ b/tests/cli/resolve.rs @@ -1,12 +1,12 @@ use crate::common::default_args; use star_setup::{ cli::{resolve_bool, resolve_with_config, BuildType}, - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, }; /// Helper to quickly build a `SetupConfig` with a populated profile entry. -fn config_with_entry(name: &str, entry: ConfigEntry) -> SetupConfig { - let mut config = SetupConfig::new(); +fn config_with_entry(name: &str, entry: ConfigEntry) -> Config { + let mut config = Config::new(); config.configs.insert(name.to_string(), entry); config } @@ -80,7 +80,7 @@ fn test_resolve_bool() { /* ===== RESOLVE_WITH_CONFIG ===== */ #[test] fn test_resolve_with_config_defaults_when_no_config() { - let config = SetupConfig::new(); + let config = Config::new(); let resolved = resolve_with_config(default_args(), &config).unwrap(); assert!(!resolved.connection.ssh); assert_eq!(resolved.build.build_type, BuildType::Debug); @@ -130,7 +130,7 @@ fn test_resolve_with_config_cli_overrides_config() { #[test] fn test_resolve_with_config_errors_on_missing_config_name() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.config_name = Some("nonexistent".to_string()); @@ -139,7 +139,7 @@ fn test_resolve_with_config_errors_on_missing_config_name() { #[test] fn test_resolve_with_config_mono_repo_from_repos() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.mono.repos = Some(vec!["user/lib1".to_string()]); @@ -149,7 +149,7 @@ fn test_resolve_with_config_mono_repo_from_repos() { #[test] fn test_resolve_with_config_mono_repo_from_profile() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.mono.profile = Some("myprofile".to_string()); diff --git a/tests/commands/mono/mode.rs b/tests/commands/mono/mode.rs index e626f4f..8aac778 100644 --- a/tests/commands/mono/mode.rs +++ b/tests/commands/mono/mode.rs @@ -1,7 +1,5 @@ use crate::common::{default_resolved_mono, with_ctx, with_ctx_runner, MockRunner}; -use star_setup::{ - cli::BuildSystem, commands::mono_repo_mode, config::SetupConfig, ctx::DryRunRunner, -}; +use star_setup::{cli::BuildSystem, commands::mono_repo_mode, config::Config, ctx::DryRunRunner}; use std::{ fs::{create_dir_all, read_dir, write}, path::Path, @@ -23,7 +21,7 @@ fn test_mono_repo_mode_clones_and_configures() { make_cmake_repo(&repos_path, "user-lib1"); make_cmake_repo(&repos_path, "user-test-repo"); - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); }); let out = String::from_utf8(output).unwrap(); @@ -39,7 +37,7 @@ fn test_mono_repo_mode_dry_run_makes_no_fs_changes() { with_ctx(DryRunRunner, |tmp_path, ctx| { ctx.flags.dry_run = true; - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); assert!(read_dir(tmp_path).unwrap().next().is_none()); }); @@ -56,7 +54,7 @@ fn test_mono_repo_mode_dry_run_with_build_system_makes_no_fs_changes() { with_ctx(DryRunRunner, |tmp_path, ctx| { ctx.flags.dry_run = true; - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); assert!( read_dir(tmp_path).unwrap().next().is_none(), @@ -77,7 +75,7 @@ fn test_mono_repo_mode_with_build_system_flag() { make_cmake_repo(&repos_path, "user-lib1"); make_cmake_repo(&repos_path, "user-test-repo"); - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); }); assert!(runner.calls.iter().any(|(cmd, _)| cmd[0] == "cmake")); diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index 6b12c14..d7cf9c9 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -1,8 +1,13 @@ use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; use star_setup::{ cli::BuildSystem, - commands::{generate_mono_config, resolve_repos_for_mono, resolve_test_repo}, - config::SetupConfig, + commands::{ + generate_mono_config, + mono::{resolve_profile, resolve_test_repo_for_mono}, + resolve_repos_for_mono, resolve_test_repo, + }, + config::Config, + profile::Profile, }; use std::{ fs::{create_dir_all, read_to_string, write}, @@ -52,71 +57,93 @@ fn test_resolve_test_repo_errors() { /* ===== RESOLVE_REPOS_FOR_MONO ===== */ #[test] -fn test_resolve_repos_for_mono_empty_profile_errors() { - let mut config = SetupConfig::new(); - config.profiles.insert("emptyprofile".to_string(), vec![]); +fn test_resolve_test_repo_for_mono_errors_when_profile_empty() { + let mut config = Config::new(); + config + .profiles + .insert("emptyprofile".to_string(), Profile::default()); let mut args = default_resolved(); + args.repo = None; args.mono.profile = Some("emptyprofile".to_string()); - with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("has no repositories")); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert!(resolve_test_repo_for_mono(&args, profile).is_err()); }); } #[test] -fn test_resolve_repos_for_mono_with_profile() { - let mut config = SetupConfig::new(); +fn test_resolve_test_repo_for_mono_prefers_positional_over_profile() { + let mut config = Config::new(); config.profiles.insert( "myprofile".to_string(), - vec!["user/lib1".to_string(), "user/lib2".to_string()], + Profile { + test_repo: Some("user/other".to_string()), + deps: vec![], + }, ); - let mut args = default_resolved(); - args.mono.profile = Some("myprofile".to_string()); - + let args = default_resolved(); // args.repo already Some("user/repo") with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), vec!["user/lib1", "user/lib2"]); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert_eq!( + resolve_test_repo_for_mono(&args, profile), + Ok("user/repo".to_string()) + ); }); } #[test] -fn test_resolve_repos_for_mono_with_explicit_repos() { - let config = SetupConfig::new(); +fn test_resolve_test_repo_for_mono_falls_back_to_profile() { + let mut config = Config::new(); + config.profiles.insert( + "myprofile".to_string(), + Profile { + test_repo: Some("user/app".to_string()), + deps: vec![], + }, + ); let mut args = default_resolved(); - args.mono.repos = Some(vec!["user/lib1".to_string(), "user/lib2".to_string()]); - + args.repo = None; + args.mono.profile = Some("myprofile".to_string()); with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), vec!["user/lib1", "user/lib2"]); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert_eq!( + resolve_test_repo_for_mono(&args, profile), + Ok("user/app".to_string()) + ); }); } #[test] -fn test_resolve_repos_for_mono_no_repos_or_profile_errors() { - let config = SetupConfig::new(); +fn test_resolve_repos_for_mono_with_profile() { + let profile = Profile { + test_repo: None, + deps: vec!["user/lib1".to_string(), "user/lib2".to_string()], + }; let args = default_resolved(); + assert_eq!( + resolve_repos_for_mono(&args, Some(&profile)), + vec!["user/lib1", "user/lib2"] + ); +} - with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("No repos or profile specified")); - }); +#[test] +fn test_resolve_repos_for_mono_with_explicit_repos() { + let mut args = default_resolved(); + args.mono.repos = Some(vec!["user/lib1".to_string(), "user/lib2".to_string()]); + assert_eq!( + resolve_repos_for_mono(&args, None), + vec!["user/lib1", "user/lib2"] + ); } #[test] -fn test_resolve_repos_for_mono_profile_not_found_errors() { - let config = SetupConfig::new(); +fn test_resolve_profile_not_found_errors() { + let config = Config::new(); let mut args = default_resolved(); args.mono.profile = Some("nonexistent".to_string()); with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); + let result = resolve_profile(&args, &config, io); assert!(result.is_err()); assert!(result.unwrap_err().contains("not found")); }); diff --git a/tests/common/args.rs b/tests/common/args.rs index c8152a0..5c6756b 100644 --- a/tests/common/args.rs +++ b/tests/common/args.rs @@ -5,7 +5,7 @@ use star_setup::{ resolve_with_config, Args, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, }, - config::SetupConfig, + config::Config, }; pub fn default_args() -> Args { @@ -54,18 +54,18 @@ pub fn default_resolved() -> ResolvedArgs { let mut args = default_args(); args.repo = Some("user/repo".to_string()); args.build.no_build = true; - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } pub fn default_resolved_with_no_build(no_build: bool) -> ResolvedArgs { let mut args = default_args(); args.repo = Some("user/repo".to_string()); args.build.no_build = no_build; - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } pub fn default_resolved_interactive() -> ResolvedArgs { - resolve_with_config(default_args(), &SetupConfig::new()).unwrap() + resolve_with_config(default_args(), &Config::new()).unwrap() } pub fn default_resolved_mono(repos: Vec) -> ResolvedArgs { @@ -75,5 +75,5 @@ pub fn default_resolved_mono(repos: Vec) -> ResolvedArgs { args.build.no_build = true; args.mono.mono_repo = true; args.mono.repos = Some(repos); - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } diff --git a/tests/config/crud.rs b/tests/config/crud.rs index 48d8fe0..2e43fc3 100644 --- a/tests/config/crud.rs +++ b/tests/config/crud.rs @@ -1,7 +1,7 @@ use crate::common::{make_flags, with_io_dir, with_io_input_output, with_io_output}; use star_setup::config::{ add_config, create_default_config, has_config, insert_config, list_configs, remove_config, - remove_config_entry, save_config, ConfigEntry, SetupConfig, + remove_config_entry, save_config, Config, ConfigEntry, }; use std::fs::{read_to_string, write}; use tempfile::TempDir; @@ -9,7 +9,7 @@ use tempfile::TempDir; /* ===== HAS_CONFIG ===== */ #[test] fn test_has_config_false() { - let config = SetupConfig::new(); + let config = Config::new(); assert!(!has_config(&config, "nonexistent")); } @@ -17,7 +17,7 @@ fn test_has_config_false() { fn test_add_config_inserts_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); add_config( @@ -39,7 +39,7 @@ fn test_add_config_inserts_and_saves() { fn test_add_config_aborts_when_exists_and_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); insert_config( &mut config, @@ -66,14 +66,14 @@ fn test_add_config_aborts_when_exists_and_not_confirmed() { /* ===== INSERT_CONFIG ===== */ #[test] fn test_has_config_true() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); assert!(has_config(&config, "myconfig")); } #[test] fn test_insert_config() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config( &mut config, "myconfig", @@ -88,7 +88,7 @@ fn test_insert_config() { #[test] fn test_remove_config_entry_exists() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); assert!(remove_config_entry(&mut config, "myconfig")); assert!(!config.configs.contains_key("myconfig")); @@ -120,7 +120,7 @@ fn test_create_default_config_aborts_when_exists_and_not_confirmed() { #[test] fn test_list_configs_empty() { let ((), out) = with_io_output(|io| { - let config = SetupConfig::new(); + let config = Config::new(); list_configs(&config, io); }); assert!(out.contains("No configurations created")); @@ -129,7 +129,7 @@ fn test_list_configs_empty() { #[test] fn test_list_configs_with_entries() { let ((), out) = with_io_output(|io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); list_configs(&config, io); }); @@ -140,7 +140,7 @@ fn test_list_configs_with_entries() { /* ===== REMOVE_CONFIG_ENTRY ===== */ #[test] fn test_remove_config_entry_missing() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); assert!(!remove_config_entry(&mut config, "nonexistent")); } @@ -149,7 +149,7 @@ fn test_remove_config_entry_missing() { fn test_remove_config_removes_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); insert_config(&mut config, "myconfig", ConfigEntry::default()); save_config(&mut config, false, &mut io.output).unwrap(); @@ -161,7 +161,7 @@ fn test_remove_config_removes_and_saves() { #[test] fn test_remove_config_not_found() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); with_io_output(|io| { remove_config(&mut config, "nonexistent", true, io, make_flags()).unwrap(); }); @@ -171,7 +171,7 @@ fn test_remove_config_not_found() { fn test_remove_config_aborts_when_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); insert_config(&mut config, "myconfig", ConfigEntry::default()); diff --git a/tests/config/io.rs b/tests/config/io.rs index 42e0122..a73d143 100644 --- a/tests/config/io.rs +++ b/tests/config/io.rs @@ -1,7 +1,7 @@ use crate::common::with_io_output; use star_setup::{ cli::BuildType, - config::{insert_config, load_config, save_config, ConfigEntry, SetupConfig}, + config::{insert_config, load_config, save_config, Config, ConfigEntry}, }; use std::{fs::write, path::PathBuf}; use tempfile::TempDir; @@ -12,7 +12,7 @@ fn test_save_and_load_roundtrip() { let tmp = TempDir::new().unwrap(); let path = tmp.path().join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); config.configs.insert( "default".to_string(), @@ -76,8 +76,8 @@ fn test_load_config_first_valid_wins() { let path1 = tmp1.path().join(".star-setup.json"); let path2 = tmp2.path().join(".star-setup.json"); - let mut config1 = SetupConfig::new(); - let mut config2 = SetupConfig::new(); + let mut config1 = Config::new(); + let mut config2 = Config::new(); with_io_output(|io| { config1.path = Some(path1.clone()); @@ -103,7 +103,7 @@ fn test_load_config_falls_through_invalid_to_valid() { write(&path1, "{invalid json").unwrap(); - let mut config2 = SetupConfig::new(); + let mut config2 = Config::new(); config2.path = Some(path2.clone()); insert_config(&mut config2, "second", ConfigEntry::default()); diff --git a/tests/profile.rs b/tests/profile.rs index e08ad54..a84ee34 100644 --- a/tests/profile.rs +++ b/tests/profile.rs @@ -5,3 +5,5 @@ mod common; mod crud; #[path = "profile/display.rs"] mod display; +#[path = "profile/types.rs"] +mod types; diff --git a/tests/profile/crud.rs b/tests/profile/crud.rs index 3471e91..88b44a8 100644 --- a/tests/profile/crud.rs +++ b/tests/profile/crud.rs @@ -1,14 +1,16 @@ use crate::common::{make_flags, with_io_dir, with_io_input_output, with_io_output}; use star_setup::{ - config::{load_config, save_config, SetupConfig}, - profile::{add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry}, + config::{load_config, save_config, Config}, + profile::{ + add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry, Profile, + }, }; use tempfile::TempDir; /* ===== HAS_PROFILE ===== */ #[test] fn test_has_profile_false() { - let config = SetupConfig::new(); + let config = Config::new(); assert!(!has_profile(&config, "nonexistent")); } @@ -16,11 +18,22 @@ fn test_has_profile_false() { fn test_add_profile_inserts_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); - let args = vec!["myprofile".to_string(), "user/repo1".to_string()]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); + assert!(has_profile(&config, "myprofile")); assert!(path.exists()); }); @@ -31,21 +44,24 @@ fn test_add_profile_inserts_and_saves() { fn test_save_and_load_profile_roundtrip() { let tmp = TempDir::new().unwrap(); let path = tmp.path().join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); + insert_profile( &mut config, "myprofile", - vec!["user/repo1".to_string(), "user/repo2".to_string()], + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string(), "user/repo2".to_string()], + }, ); with_io_output(|io| { save_config(&mut config, false, &mut io.output).unwrap(); - let loaded = load_config(&[path], false, false, &mut io.output); assert!(loaded.profiles.contains_key("myprofile")); assert_eq!( - loaded.profiles["myprofile"], + loaded.profiles["myprofile"].deps, vec!["user/repo1", "user/repo2"] ); }); @@ -54,73 +70,94 @@ fn test_save_and_load_profile_roundtrip() { /* ===== INSERT_PROFILE ===== */ #[test] fn test_insert_profile() { - let mut config = SetupConfig::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + let mut config = Config::new(); + + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + ); + assert!(config.profiles.contains_key("myprofile")); } #[test] fn test_remove_profile_entry_exists() { - let mut config = SetupConfig::new(); - insert_profile(&mut config, "myprofile", vec![]); + let mut config = Config::new(); + + insert_profile(&mut config, "myprofile", Profile::default()); + assert!(remove_profile_entry(&mut config, "myprofile")); assert!(!config.profiles.contains_key("myprofile")); } #[test] fn test_has_profile_true() { - let mut config = SetupConfig::new(); - insert_profile(&mut config, "myprofile", vec![]); - assert!(has_profile(&config, "myprofile")); -} + let mut config = Config::new(); -/* ===== ADD_PROFILE ===== */ -#[test] -fn test_add_profile_errors_on_insufficient_args() { - let mut config = SetupConfig::new(); - let args = vec!["myprofile".to_string()]; - with_io_output(|io| { - let result = add_profile(&mut config, &args, true, io, make_flags()); - assert!(result.is_err()); - }); -} + insert_profile(&mut config, "myprofile", Profile::default()); -#[test] -fn test_add_profile_errors_on_empty_args() { - let mut config = SetupConfig::new(); - with_io_output(|io| { - let result = add_profile(&mut config, &[], true, io, make_flags()); - assert!(result.is_err()); - }); + assert!(has_profile(&config, "myprofile")); } +/* ===== ADD_PROFILE ===== */ #[test] fn test_add_profile_overwrites_existing() { with_io_dir(|tmp, io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); - insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); - let args = vec!["myprofile".to_string(), "new/repo".to_string()]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"], vec!["new/repo"]); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["old/repo".to_string()], + }, + ); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["new/repo".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); + assert_eq!(config.profiles["myprofile"].deps, vec!["new/repo"]); }); } #[test] fn test_add_profile_multiple_repos() { with_io_dir(|tmp, io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); - let args = vec![ - "myprofile".to_string(), - "user/repo1".to_string(), - "user/repo2".to_string(), - "user/repo3".to_string(), - ]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"].len(), 3); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec![ + "user/repo1".to_string(), + "user/repo2".to_string(), + "user/repo3".to_string(), + ], + }, + true, + io, + make_flags(), + ) + .unwrap(); + + assert_eq!(config.profiles["myprofile"].deps.len(), 3); }); } @@ -128,20 +165,37 @@ fn test_add_profile_multiple_repos() { fn test_add_profile_aborts_when_exists_and_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); - insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); - let args = vec!["myprofile".to_string(), "new/repo".to_string()]; - add_profile(&mut config, &args, false, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"], vec!["old/repo"]); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["old/repo".to_string()], + }, + ); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["new/repo".to_string()], + }, + false, + io, + make_flags(), + ) + .unwrap(); + assert_eq!(config.profiles["myprofile"].deps, vec!["old/repo"]); }); } /* ===== REMOVE_PROFILE_ENTRY ===== */ #[test] fn test_remove_profile_entry_missing() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); assert!(!remove_profile_entry(&mut config, "nonexistent")); } @@ -150,19 +204,33 @@ fn test_remove_profile_entry_missing() { fn test_remove_profile_removes_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); + save_config(&mut config, false, &mut io.output).unwrap(); remove_profile(&mut config, "myprofile", true, io, make_flags()).unwrap(); + assert!(!has_profile(&config, "myprofile")); }); } #[test] fn test_remove_profile_not_found() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); with_io_output(|io| { remove_profile(&mut config, "nonexistent", true, io, make_flags()).unwrap(); }); @@ -171,10 +239,23 @@ fn test_remove_profile_not_found() { #[test] fn test_remove_profile_aborts_when_not_confirmed() { with_io_input_output(b"n\n", |io| { - let mut config = SetupConfig::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + let mut config = Config::new(); + + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); remove_profile(&mut config, "myprofile", false, io, make_flags()).unwrap(); + assert!(has_profile(&config, "myprofile")); }); } diff --git a/tests/profile/display.rs b/tests/profile/display.rs index 6b127d3..8e1bda3 100644 --- a/tests/profile/display.rs +++ b/tests/profile/display.rs @@ -1,13 +1,13 @@ use crate::common::with_io_output; use star_setup::{ - config::SetupConfig, - profile::{insert_profile, list_profiles}, + config::Config, + profile::{insert_profile, list_profiles, Profile}, }; #[test] fn test_list_profiles_empty() { let ((), out) = with_io_output(|io| { - let config = SetupConfig::new(); + let config = Config::new(); list_profiles(&config, io); }); assert!(out.contains("No profiles configured")); @@ -16,8 +16,15 @@ fn test_list_profiles_empty() { #[test] fn test_list_profiles_with_entries() { let ((), out) = with_io_output(|io| { - let mut config = SetupConfig::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + let mut config = Config::new(); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + ); list_profiles(&config, io); }); assert!(out.contains("myprofile")); diff --git a/tests/profile/types.rs b/tests/profile/types.rs new file mode 100644 index 0000000..cb67eef --- /dev/null +++ b/tests/profile/types.rs @@ -0,0 +1,11 @@ +use star_setup::profile::Profile; + +#[test] +fn test_profile_from_args_errors_when_empty() { + assert!(Profile::from_args(None, vec![]).is_err()); +} + +#[test] +fn test_profile_from_args_test_repo_only() { + assert!(Profile::from_args(Some("user/app".to_string()), vec![]).is_ok()); +}