twc-rs 4.0.4

Fast single-binary CLI and interactive TUI dashboard for Timeweb Cloud
Documentation
use clap::Parser;

use super::*;

fn parse(args: &[&str]) -> Cli {
    Cli::try_parse_from(std::iter::once("twc-rs").chain(args.iter().copied()))
        .expect("should parse successfully")
}

#[test]
fn default_format_is_table() {
    let cli = parse(&["config", "show"]);
    assert_eq!(cli.format, "table");
}

#[test]
fn format_flag_json() {
    let cli = parse(&["--format", "json", "config", "show"]);
    assert_eq!(cli.format, "json");
}

#[test]
fn format_short_flag() {
    let cli = parse(&["-f", "quiet", "config", "show"]);
    assert_eq!(cli.format, "quiet");
}

#[test]
fn format_json_alias() {
    let cli = parse(&["--format", "js", "config", "show"]);
    assert_eq!(cli.format, "js");
}

#[test]
fn format_quiet_alias() {
    let cli = parse(&["--format", "q", "config", "show"]);
    assert_eq!(cli.format, "q");
}

#[test]
fn token_flag() {
    let cli = parse(&["--token", "abc123", "config", "show"]);
    assert_eq!(cli.token.as_deref(), Some("abc123"));
}

#[test]
fn token_short_flag() {
    let cli = parse(&["-t", "xyz", "config", "show"]);
    assert_eq!(cli.token.as_deref(), Some("xyz"));
}

#[test]
fn no_token_flag() {
    let cli = parse(&["config", "show"]);
    assert!(cli.token.is_none());
}

#[test]
fn server_list_subcommand() {
    let cli = parse(&["server", "list"]);
    assert!(matches!(
        cli.command,
        Commands::Server(ServerCommands::List { .. })
    ));
}

#[test]
fn server_list_with_limit_offset() {
    let cli = parse(&["server", "list", "--limit", "10", "--offset", "5"]);
    match &cli.command {
        Commands::Server(ServerCommands::List {
            limit,
            offset
        }) => {
            assert_eq!(*limit, Some(10));
            assert_eq!(*offset, Some(5));
        }
        _ => panic!("expected ServerCommands::List")
    }
}

#[test]
fn server_list_with_only_limit() {
    let cli = parse(&["server", "list", "--limit", "20"]);
    match &cli.command {
        Commands::Server(ServerCommands::List {
            limit,
            offset
        }) => {
            assert_eq!(*limit, Some(20));
            assert_eq!(*offset, None);
        }
        _ => panic!("expected ServerCommands::List")
    }
}

#[test]
fn server_list_with_only_offset() {
    let cli = parse(&["server", "list", "--offset", "10"]);
    match &cli.command {
        Commands::Server(ServerCommands::List {
            limit,
            offset
        }) => {
            assert_eq!(*limit, None);
            assert_eq!(*offset, Some(10));
        }
        _ => panic!("expected ServerCommands::List")
    }
}

#[test]
fn server_info_subcommand() {
    let cli = parse(&["server", "info", "--id", "42"]);
    match &cli.command {
        Commands::Server(ServerCommands::Info {
            id
        }) => {
            assert_eq!(*id, 42);
        }
        _ => panic!("expected ServerCommands::Info")
    }
}

#[test]
fn server_info_id() {
    let cli = parse(&["server", "info", "--id", "99"]);
    match &cli.command {
        Commands::Server(ServerCommands::Info {
            id
        }) => {
            assert_eq!(*id, 99);
        }
        _ => panic!("expected ServerCommands::Info")
    }
}

#[test]
fn server_delete_subcommand() {
    let cli = parse(&["server", "delete", "--id", "7"]);
    match &cli.command {
        Commands::Server(ServerCommands::Delete {
            id
        }) => {
            assert_eq!(*id, 7);
        }
        _ => panic!("expected ServerCommands::Delete")
    }
}

#[test]
fn server_reboot_subcommand() {
    let cli = parse(&["server", "reboot", "--id", "3"]);
    match &cli.command {
        Commands::Server(ServerCommands::Reboot {
            id
        }) => {
            assert_eq!(*id, 3);
        }
        _ => panic!("expected ServerCommands::Reboot")
    }
}

#[test]
fn ssh_list_subcommand() {
    let cli = parse(&["ssh", "list"]);
    assert!(matches!(cli.command, Commands::Ssh(SshCommands::List)));
}

#[test]
fn ssh_delete_subcommand() {
    let cli = parse(&["ssh", "delete", "--id", "99"]);
    match &cli.command {
        Commands::Ssh(SshCommands::Delete {
            id
        }) => {
            assert_eq!(*id, 99);
        }
        _ => panic!("expected SshCommands::Delete")
    }
}

#[test]
fn ssh_attach_subcommand() {
    let cli = parse(&[
        "ssh", "attach", "--server", "12", "--key", "5", "--key", "7"
    ]);
    match &cli.command {
        Commands::Ssh(SshCommands::Attach {
            server,
            key
        }) => {
            assert_eq!(*server, 12);
            assert_eq!(key, &[5, 7]);
        }
        _ => panic!("expected SshCommands::Attach")
    }
}

#[test]
fn ssh_detach_subcommand() {
    let cli = parse(&["ssh", "detach", "--server", "12", "--key", "5"]);
    match &cli.command {
        Commands::Ssh(SshCommands::Detach {
            server,
            key
        }) => {
            assert_eq!(*server, 12);
            assert_eq!(*key, 5);
        }
        _ => panic!("expected SshCommands::Detach")
    }
}

#[test]
fn project_list_subcommand() {
    let cli = parse(&["project", "list"]);
    assert!(matches!(
        cli.command,
        Commands::Project(ProjectCommands::List)
    ));
}

#[test]
fn project_create_subcommand() {
    let cli = parse(&["project", "create", "--name", "my-project"]);
    match &cli.command {
        Commands::Project(ProjectCommands::Create {
            name,
            description
        }) => {
            assert_eq!(name, "my-project");
            assert!(description.is_none());
        }
        _ => panic!("expected ProjectCommands::Create")
    }
}

#[test]
fn project_create_with_description() {
    let cli = parse(&[
        "project",
        "create",
        "--name",
        "web",
        "--description",
        "Production web app"
    ]);
    match &cli.command {
        Commands::Project(ProjectCommands::Create {
            name,
            description
        }) => {
            assert_eq!(name, "web");
            assert_eq!(description.as_deref(), Some("Production web app"));
        }
        _ => panic!("expected ProjectCommands::Create")
    }
}

#[test]
fn project_delete_subcommand() {
    let cli = parse(&["project", "delete", "--id", "5"]);
    match &cli.command {
        Commands::Project(ProjectCommands::Delete {
            id
        }) => {
            assert_eq!(*id, 5);
        }
        _ => panic!("expected ProjectCommands::Delete")
    }
}

#[test]
fn config_show_subcommand() {
    let cli = parse(&["config", "show"]);
    assert!(matches!(
        cli.command,
        Commands::Config(ConfigCommands::Show)
    ));
}

#[test]
fn config_set_token_subcommand() {
    let cli = parse(&["config", "set-token", "--token", "secret"]);
    match &cli.command {
        Commands::Config(ConfigCommands::SetToken {
            token, ..
        }) => {
            assert_eq!(token, "secret");
        }
        _ => panic!("expected ConfigCommands::SetToken")
    }
}

#[test]
fn config_set_token_flag() {
    let cli = parse(&["config", "set-token", "--token", "abc"]);
    match &cli.command {
        Commands::Config(ConfigCommands::SetToken {
            token, ..
        }) => {
            assert_eq!(token, "abc");
        }
        _ => panic!("expected ConfigCommands::SetToken")
    }
}

#[test]
fn invalid_subcommand_fails() {
    let result = Cli::try_parse_from(["twc-rs", "nonexistent"]);
    assert!(result.is_err());
}

#[test]
fn server_requires_subcommand() {
    let result = Cli::try_parse_from(["twc-rs", "server"]);
    assert!(result.is_err());
}

#[test]
fn ssh_requires_subcommand() {
    let result = Cli::try_parse_from(["twc-rs", "ssh"]);
    assert!(result.is_err());
}

#[test]
fn project_requires_subcommand() {
    let result = Cli::try_parse_from(["twc-rs", "project"]);
    assert!(result.is_err());
}

#[test]
fn config_requires_subcommand() {
    let result = Cli::try_parse_from(["twc-rs", "config"]);
    assert!(result.is_err());
}

#[test]
fn server_info_requires_id() {
    let result = Cli::try_parse_from(["twc-rs", "server", "info"]);
    assert!(result.is_err());
}

#[test]
fn ssh_delete_requires_id() {
    let result = Cli::try_parse_from(["twc-rs", "ssh", "delete"]);
    assert!(result.is_err());
}

#[test]
fn project_create_requires_name() {
    let result = Cli::try_parse_from(["twc-rs", "project", "create"]);
    assert!(result.is_err());
}

#[test]
fn global_flags_before_subcommand() {
    let cli = parse(&["-t", "tok", "-f", "json", "config", "show"]);
    assert_eq!(cli.token.as_deref(), Some("tok"));
    assert_eq!(cli.format, "json");
    assert!(matches!(
        cli.command,
        Commands::Config(ConfigCommands::Show)
    ));
}

#[test]
fn help_returns_error_with_help_displayed() {
    let result = Cli::try_parse_from(["twc-rs", "--help"]);
    assert!(result.is_err());
    let err = result.unwrap_err();
    let help_text = err.render().to_string();
    assert!(help_text.contains("Timeweb Cloud CLI"));
    assert!(help_text.contains("server"));
    assert!(help_text.contains("ssh"));
    assert!(help_text.contains("project"));
    assert!(help_text.contains("config"));
}