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"),
+ }
+ }
+}