diff --git a/Tasks.md b/Tasks.md index f60b9e8..9d5134c 100644 --- a/Tasks.md +++ b/Tasks.md @@ -70,7 +70,6 @@ This document defines the complete project roadmap and task tracking system for | ID | Title | Status | Type | |---|---|---|---| -| [NUT-017](#nut-017) | Implement Multi-Account Switching (GUI + CLI) | Triage | Integration | | [NUT-018](#nut-018) | Implement Packaging for macOS, Windows, Linux | Triage | Chore | | [NUT-019](#nut-019) | Implement Homebrew/Winget/Chocolatey Manifests | Triage | Chore | | [NUT-020](#nut-020) | Write Documentation + Examples | Triage | Chore | @@ -93,6 +92,7 @@ This document defines the complete project roadmap and task tracking system for | [NUT-014](#nut-014) | Implement GUI Credential Management UI | Fixed | Feature | | [NUT-015](#nut-015) | Implement GUI Upload Progress Bars | Fixed | Feature | | [NUT-016](#nut-016) | Implement Shared Auth Token Reuse | Fixed | Integration | +| [NUT-017](#nut-017) | Implement Multi-Account Switching (GUI + CLI) | Fixed | Integration | | [NUT-021](#nut-021) | Support Headless & SSH Remote Authentication Modes | Fixed | Feature | --- @@ -422,19 +422,19 @@ Ensure both CLI and GUI reuse the same credential store and cached tokens. - NUT-012 - + ### Implement Multi-Account Switching (GUI + CLI) **ID:** NUT-017 -**Status:** Triage +**Status:** Fixed **Type:** Integration **Description:** Add multi-account switching to both CLI and GUI. **Requirements:** -- [ ] CLI `--account` flag -- [ ] GUI dropdown -- [ ] Shared backend logic +- [x] CLI `--account` flag +- [x] GUI dropdown +- [x] Shared backend logic **Dependencies:** - NUT-007 diff --git a/cli/src/main.rs b/cli/src/main.rs index d4b388a..6a1e0d8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -12,7 +12,7 @@ use nextcloud_client::{ ProgressCallback, ProgressEvent, Result, UploadOptions, }; -#[derive(Parser, Debug)] +#[derive(Parser, Debug, PartialEq)] #[command( name = "nut", author = "Tom Hicks ", @@ -25,7 +25,7 @@ struct Cli { command: Commands, } -#[derive(Subcommand, Debug)] +#[derive(Subcommand, Debug, PartialEq)] enum Commands { /// Log in to a Nextcloud server using browser authorization (Login Flow v2) or credentials Login(LoginArgs), @@ -41,7 +41,7 @@ enum Commands { Accounts(AccountListArgs), } -#[derive(Args, Debug)] +#[derive(Args, Debug, PartialEq)] struct LoginArgs { /// Base URL of the Nextcloud instance (e.g. https://cloud.example.com) server_url: String, @@ -71,7 +71,7 @@ struct LoginArgs { app_password: Option, } -#[derive(Args, Debug, Clone, Default)] +#[derive(Args, Debug, Clone, Default, PartialEq)] struct OutputFormatArgs { /// Format output as JSON #[arg(long, conflicts_with_all = ["tsv", "url_only", "direct_url_only"])] @@ -100,7 +100,7 @@ impl OutputFormatArgs { } } -#[derive(Args, Debug)] +#[derive(Args, Debug, PartialEq)] struct UploadArgs { /// Path to local file(s) or directories to upload #[arg(value_name = "FILE")] @@ -150,7 +150,7 @@ struct UploadArgs { format: OutputFormatArgs, } -#[derive(Subcommand, Debug)] +#[derive(Subcommand, Debug, PartialEq)] enum AccountCommands { /// List all configured Nextcloud accounts List(AccountListArgs), @@ -168,7 +168,7 @@ enum AccountCommands { }, } -#[derive(Args, Debug, Clone, Default)] +#[derive(Args, Debug, Clone, Default, PartialEq)] struct AccountListArgs { /// Format output as JSON #[arg(long, conflicts_with = "tsv")] @@ -771,3 +771,55 @@ fn handle_account_delete(account: &str) -> Result<()> { println!("\x1b[1;32m✓\x1b[0m Account '\x1b[1m{}\x1b[0m' deleted.", account); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cli_account_flag_parsing() { + let parsed = Cli::try_parse_from(&[ + "nut", + "upload", + "--account", + "alice@cloud.example.com", + "document.pdf", + ]) + .unwrap(); + + match parsed.command { + Commands::Upload(args) => { + assert_eq!(args.account.as_deref(), Some("alice@cloud.example.com")); + assert_eq!(args.files, vec![PathBuf::from("document.pdf")]); + } + _ => panic!("Expected Upload command"), + } + } + + #[test] + fn test_cli_account_subcommands_parsing() { + let parsed = Cli::try_parse_from(&["nut", "account", "default", "Work"]).unwrap(); + match parsed.command { + Commands::Account(AccountCommands::Default { account }) => { + assert_eq!(account, "Work"); + } + _ => panic!("Expected Account Default command"), + } + + let parsed_del = Cli::try_parse_from(&["nut", "account", "delete", "bob@cloud.com"]).unwrap(); + match parsed_del.command { + Commands::Account(AccountCommands::Delete { account }) => { + assert_eq!(account, "bob@cloud.com"); + } + _ => panic!("Expected Account Delete command"), + } + + let parsed_list = Cli::try_parse_from(&["nut", "accounts", "--json"]).unwrap(); + match parsed_list.command { + Commands::Accounts(args) => { + assert!(args.json); + } + _ => panic!("Expected Accounts list command"), + } + } +}