diff --git a/crates-cli/yaak-cli/PLAN.md b/crates-cli/yaak-cli/PLAN.md index 22a7a6db..604f6d8e 100644 --- a/crates-cli/yaak-cli/PLAN.md +++ b/crates-cli/yaak-cli/PLAN.md @@ -145,7 +145,7 @@ Existing behavior stays the same, just reorganized. Remove the `get` command. ### Phase 2: Add missing CRUD commands -Status: in progress (request `show` and `delete` implemented) +Status: in progress (`show`/`create`/`delete` implemented for workspace, request, folder, environment; JSON update flow pending) 1. `workspace show ` 2. `workspace create --name ` (and `--json`) diff --git a/crates-cli/yaak-cli/README.md b/crates-cli/yaak-cli/README.md new file mode 100644 index 00000000..6ee8eed8 --- /dev/null +++ b/crates-cli/yaak-cli/README.md @@ -0,0 +1,69 @@ +# yaak-cli + +Command-line interface for Yaak. + +## Command Overview + +Current top-level commands: + +```text +yaakcli send +yaakcli workspace list +yaakcli workspace show +yaakcli workspace create --name +yaakcli workspace delete [--yes] +yaakcli request list +yaakcli request show +yaakcli request send +yaakcli request create --name --url [--method GET] +yaakcli request delete [--yes] +yaakcli folder list +yaakcli folder show +yaakcli folder create --name +yaakcli folder delete [--yes] +yaakcli environment list +yaakcli environment show +yaakcli environment create --name +yaakcli environment delete [--yes] +``` + +Global options: + +- `--data-dir `: use a custom data directory +- `-e, --environment `: environment to use during request rendering/sending +- `-v, --verbose`: verbose logging and send output + +Notes: + +- `send` is currently a shortcut for sending an HTTP request ID. +- `delete` commands prompt for confirmation unless `--yes` is provided. +- In non-interactive mode, `delete` commands require `--yes`. + +## Examples + +```bash +yaakcli workspace list +yaakcli workspace create --name "My Workspace" +yaakcli workspace show wk_abc +yaakcli request list wk_abc +yaakcli request show rq_abc +yaakcli request create wk_abc --name "Users" --url "https://api.example.com/users" +yaakcli request send rq_abc -e ev_abc +yaakcli request delete rq_abc --yes +yaakcli folder create wk_abc --name "Auth" +yaakcli environment create wk_abc --name "Production" +``` + +## Roadmap + +Planned command expansion (JSON create/update, request schema, and polymorphic send) is tracked in `PLAN.md`. + +When command behavior changes, update this README and verify with: + +```bash +cargo run -q -p yaak-cli -- --help +cargo run -q -p yaak-cli -- request --help +cargo run -q -p yaak-cli -- workspace --help +cargo run -q -p yaak-cli -- folder --help +cargo run -q -p yaak-cli -- environment --help +``` diff --git a/crates-cli/yaak-cli/src/cli.rs b/crates-cli/yaak-cli/src/cli.rs index d9faf6cf..b942a08f 100644 --- a/crates-cli/yaak-cli/src/cli.rs +++ b/crates-cli/yaak-cli/src/cli.rs @@ -32,12 +32,10 @@ pub enum Commands { /// Request commands Request(RequestArgs), - /// Folder commands (coming soon) - #[command(hide = true)] + /// Folder commands Folder(FolderArgs), - /// Environment commands (coming soon) - #[command(hide = true)] + /// Environment commands Environment(EnvironmentArgs), } @@ -57,6 +55,29 @@ pub struct WorkspaceArgs { pub enum WorkspaceCommands { /// List all workspaces List, + + /// Show a workspace as JSON + Show { + /// Workspace ID + workspace_id: String, + }, + + /// Create a workspace + Create { + /// Workspace name + #[arg(short, long)] + name: String, + }, + + /// Delete a workspace + Delete { + /// Workspace ID + workspace_id: String, + + /// Skip confirmation prompt + #[arg(short, long)] + yes: bool, + }, } #[derive(Args)] @@ -115,7 +136,83 @@ pub enum RequestCommands { } #[derive(Args)] -pub struct FolderArgs {} +pub struct FolderArgs { + #[command(subcommand)] + pub command: FolderCommands, +} + +#[derive(Subcommand)] +pub enum FolderCommands { + /// List folders in a workspace + List { + /// Workspace ID + workspace_id: String, + }, + + /// Show a folder as JSON + Show { + /// Folder ID + folder_id: String, + }, + + /// Create a folder + Create { + /// Workspace ID + workspace_id: String, + + /// Folder name + #[arg(short, long)] + name: String, + }, + + /// Delete a folder + Delete { + /// Folder ID + folder_id: String, + + /// Skip confirmation prompt + #[arg(short, long)] + yes: bool, + }, +} #[derive(Args)] -pub struct EnvironmentArgs {} +pub struct EnvironmentArgs { + #[command(subcommand)] + pub command: EnvironmentCommands, +} + +#[derive(Subcommand)] +pub enum EnvironmentCommands { + /// List environments in a workspace + List { + /// Workspace ID + workspace_id: String, + }, + + /// Show an environment as JSON + Show { + /// Environment ID + environment_id: String, + }, + + /// Create an environment + Create { + /// Workspace ID + workspace_id: String, + + /// Environment name + #[arg(short, long)] + name: String, + }, + + /// Delete an environment + Delete { + /// Environment ID + environment_id: String, + + /// Skip confirmation prompt + #[arg(short, long)] + yes: bool, + }, +} diff --git a/crates-cli/yaak-cli/src/commands/confirm.rs b/crates-cli/yaak-cli/src/commands/confirm.rs new file mode 100644 index 00000000..038b07d2 --- /dev/null +++ b/crates-cli/yaak-cli/src/commands/confirm.rs @@ -0,0 +1,16 @@ +use std::io::{self, IsTerminal, Write}; + +pub fn confirm_delete(resource_name: &str, resource_id: &str) -> bool { + if !io::stdin().is_terminal() { + eprintln!("Refusing to delete in non-interactive mode without --yes"); + std::process::exit(1); + } + + print!("Delete {resource_name} {resource_id}? [y/N]: "); + io::stdout().flush().expect("Failed to flush stdout"); + + let mut input = String::new(); + io::stdin().read_line(&mut input).expect("Failed to read confirmation"); + + matches!(input.trim().to_lowercase().as_str(), "y" | "yes") +} diff --git a/crates-cli/yaak-cli/src/commands/environment.rs b/crates-cli/yaak-cli/src/commands/environment.rs new file mode 100644 index 00000000..a6f0414e --- /dev/null +++ b/crates-cli/yaak-cli/src/commands/environment.rs @@ -0,0 +1,64 @@ +use crate::cli::{EnvironmentArgs, EnvironmentCommands}; +use crate::commands::confirm::confirm_delete; +use crate::context::CliContext; +use yaak_models::models::Environment; +use yaak_models::util::UpdateSource; + +pub fn run(ctx: &CliContext, args: EnvironmentArgs) { + match args.command { + EnvironmentCommands::List { workspace_id } => list(ctx, &workspace_id), + EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id), + EnvironmentCommands::Create { workspace_id, name } => create(ctx, workspace_id, name), + EnvironmentCommands::Delete { environment_id, yes } => delete(ctx, &environment_id, yes), + } +} + +fn list(ctx: &CliContext, workspace_id: &str) { + let environments = + ctx.db().list_environments_ensure_base(workspace_id).expect("Failed to list environments"); + + if environments.is_empty() { + println!("No environments found in workspace {}", workspace_id); + } else { + for environment in environments { + println!("{} - {} ({})", environment.id, environment.name, environment.parent_model); + } + } +} + +fn show(ctx: &CliContext, environment_id: &str) { + let environment = ctx.db().get_environment(environment_id).expect("Failed to get environment"); + let output = + serde_json::to_string_pretty(&environment).expect("Failed to serialize environment"); + println!("{output}"); +} + +fn create(ctx: &CliContext, workspace_id: String, name: String) { + let environment = Environment { + workspace_id, + name, + parent_model: "environment".to_string(), + ..Default::default() + }; + + let created = ctx + .db() + .upsert_environment(&environment, &UpdateSource::Sync) + .expect("Failed to create environment"); + + println!("Created environment: {}", created.id); +} + +fn delete(ctx: &CliContext, environment_id: &str, yes: bool) { + if !yes && !confirm_delete("environment", environment_id) { + println!("Aborted"); + return; + } + + let deleted = ctx + .db() + .delete_environment_by_id(environment_id, &UpdateSource::Sync) + .expect("Failed to delete environment"); + + println!("Deleted environment: {}", deleted.id); +} diff --git a/crates-cli/yaak-cli/src/commands/folder.rs b/crates-cli/yaak-cli/src/commands/folder.rs new file mode 100644 index 00000000..2d12b64b --- /dev/null +++ b/crates-cli/yaak-cli/src/commands/folder.rs @@ -0,0 +1,54 @@ +use crate::cli::{FolderArgs, FolderCommands}; +use crate::commands::confirm::confirm_delete; +use crate::context::CliContext; +use yaak_models::models::Folder; +use yaak_models::util::UpdateSource; + +pub fn run(ctx: &CliContext, args: FolderArgs) { + match args.command { + FolderCommands::List { workspace_id } => list(ctx, &workspace_id), + FolderCommands::Show { folder_id } => show(ctx, &folder_id), + FolderCommands::Create { workspace_id, name } => create(ctx, workspace_id, name), + FolderCommands::Delete { folder_id, yes } => delete(ctx, &folder_id, yes), + } +} + +fn list(ctx: &CliContext, workspace_id: &str) { + let folders = ctx.db().list_folders(workspace_id).expect("Failed to list folders"); + if folders.is_empty() { + println!("No folders found in workspace {}", workspace_id); + } else { + for folder in folders { + println!("{} - {}", folder.id, folder.name); + } + } +} + +fn show(ctx: &CliContext, folder_id: &str) { + let folder = ctx.db().get_folder(folder_id).expect("Failed to get folder"); + let output = serde_json::to_string_pretty(&folder).expect("Failed to serialize folder"); + println!("{output}"); +} + +fn create(ctx: &CliContext, workspace_id: String, name: String) { + let folder = Folder { workspace_id, name, ..Default::default() }; + + let created = + ctx.db().upsert_folder(&folder, &UpdateSource::Sync).expect("Failed to create folder"); + + println!("Created folder: {}", created.id); +} + +fn delete(ctx: &CliContext, folder_id: &str, yes: bool) { + if !yes && !confirm_delete("folder", folder_id) { + println!("Aborted"); + return; + } + + let deleted = ctx + .db() + .delete_folder_by_id(folder_id, &UpdateSource::Sync) + .expect("Failed to delete folder"); + + println!("Deleted folder: {}", deleted.id); +} diff --git a/crates-cli/yaak-cli/src/commands/mod.rs b/crates-cli/yaak-cli/src/commands/mod.rs index d53be580..6c9e2a77 100644 --- a/crates-cli/yaak-cli/src/commands/mod.rs +++ b/crates-cli/yaak-cli/src/commands/mod.rs @@ -1,3 +1,6 @@ +pub mod confirm; +pub mod environment; +pub mod folder; pub mod request; pub mod send; pub mod workspace; diff --git a/crates-cli/yaak-cli/src/commands/request.rs b/crates-cli/yaak-cli/src/commands/request.rs index 3c2d4e66..15a13f29 100644 --- a/crates-cli/yaak-cli/src/commands/request.rs +++ b/crates-cli/yaak-cli/src/commands/request.rs @@ -1,9 +1,9 @@ use crate::cli::{RequestArgs, RequestCommands}; +use crate::commands::confirm::confirm_delete; use crate::context::CliContext; use log::info; use serde_json::Value; use std::collections::BTreeMap; -use std::io::{self, IsTerminal, Write}; use tokio::sync::mpsc; use yaak_http::path_placeholders::apply_path_placeholders; use yaak_http::sender::{HttpSender, ReqwestSender}; @@ -30,10 +30,7 @@ pub async fn run(ctx: &CliContext, args: RequestArgs, environment: Option<&str>, } fn list(ctx: &CliContext, workspace_id: &str) { - let requests = ctx - .db() - .list_http_requests(workspace_id) - .expect("Failed to list requests"); + let requests = ctx.db().list_http_requests(workspace_id).expect("Failed to list requests"); if requests.is_empty() { println!("No requests found in workspace {}", workspace_id); } else { @@ -61,16 +58,13 @@ fn create(ctx: &CliContext, workspace_id: String, name: String, method: String, } fn show(ctx: &CliContext, request_id: &str) { - let request = ctx - .db() - .get_http_request(request_id) - .expect("Failed to get request"); + let request = ctx.db().get_http_request(request_id).expect("Failed to get request"); let output = serde_json::to_string_pretty(&request).expect("Failed to serialize request"); println!("{output}"); } fn delete(ctx: &CliContext, request_id: &str, yes: bool) { - if !yes && !confirm_delete_request(request_id) { + if !yes && !confirm_delete("request", request_id) { println!("Aborted"); return; } @@ -82,21 +76,6 @@ fn delete(ctx: &CliContext, request_id: &str, yes: bool) { println!("Deleted request: {}", deleted.id); } -fn confirm_delete_request(request_id: &str) -> bool { - if !io::stdin().is_terminal() { - eprintln!("Refusing to delete in non-interactive mode without --yes"); - std::process::exit(1); - } - - print!("Delete request {request_id}? [y/N]: "); - io::stdout().flush().expect("Failed to flush stdout"); - - let mut input = String::new(); - io::stdin().read_line(&mut input).expect("Failed to read confirmation"); - - matches!(input.trim().to_lowercase().as_str(), "y" | "yes") -} - /// Send a request by ID and print response in the same format as legacy `send`. pub async fn send_request_by_id( ctx: &CliContext, @@ -104,18 +83,11 @@ pub async fn send_request_by_id( environment: Option<&str>, verbose: bool, ) { - let request = ctx - .db() - .get_http_request(request_id) - .expect("Failed to get request"); + let request = ctx.db().get_http_request(request_id).expect("Failed to get request"); let environment_chain = ctx .db() - .resolve_environments( - &request.workspace_id, - request.folder_id.as_deref(), - environment, - ) + .resolve_environments(&request.workspace_id, request.folder_id.as_deref(), environment) .unwrap_or_default(); let plugin_context = PluginContext::new(None, Some(request.workspace_id.clone())); @@ -169,11 +141,7 @@ pub async fn send_request_by_id( if verbose { println!(); } - println!( - "HTTP {} {}", - response.status, - response.status_reason.as_deref().unwrap_or("") - ); + println!("HTTP {} {}", response.status, response.status_reason.as_deref().unwrap_or("")); if verbose { for (name, value) in &response.headers { @@ -267,12 +235,5 @@ async fn render_http_request( let (url, url_parameters) = apply_path_placeholders(&url, &url_parameters); - Ok(HttpRequest { - url, - url_parameters, - headers, - body, - authentication, - ..request.to_owned() - }) + Ok(HttpRequest { url, url_parameters, headers, body, authentication, ..request.to_owned() }) } diff --git a/crates-cli/yaak-cli/src/commands/workspace.rs b/crates-cli/yaak-cli/src/commands/workspace.rs index ced4b9a9..160ae212 100644 --- a/crates-cli/yaak-cli/src/commands/workspace.rs +++ b/crates-cli/yaak-cli/src/commands/workspace.rs @@ -1,9 +1,15 @@ use crate::cli::{WorkspaceArgs, WorkspaceCommands}; +use crate::commands::confirm::confirm_delete; use crate::context::CliContext; +use yaak_models::models::Workspace; +use yaak_models::util::UpdateSource; pub fn run(ctx: &CliContext, args: WorkspaceArgs) { match args.command { WorkspaceCommands::List => list(ctx), + WorkspaceCommands::Show { workspace_id } => show(ctx, &workspace_id), + WorkspaceCommands::Create { name } => create(ctx, name), + WorkspaceCommands::Delete { workspace_id, yes } => delete(ctx, &workspace_id, yes), } } @@ -17,3 +23,31 @@ fn list(ctx: &CliContext) { } } } + +fn show(ctx: &CliContext, workspace_id: &str) { + let workspace = ctx.db().get_workspace(workspace_id).expect("Failed to get workspace"); + let output = serde_json::to_string_pretty(&workspace).expect("Failed to serialize workspace"); + println!("{output}"); +} + +fn create(ctx: &CliContext, name: String) { + let workspace = Workspace { name, ..Default::default() }; + let created = ctx + .db() + .upsert_workspace(&workspace, &UpdateSource::Sync) + .expect("Failed to create workspace"); + println!("Created workspace: {}", created.id); +} + +fn delete(ctx: &CliContext, workspace_id: &str, yes: bool) { + if !yes && !confirm_delete("workspace", workspace_id) { + println!("Aborted"); + return; + } + + let deleted = ctx + .db() + .delete_workspace_by_id(workspace_id, &UpdateSource::Sync) + .expect("Failed to delete workspace"); + println!("Deleted workspace: {}", deleted.id); +} diff --git a/crates-cli/yaak-cli/src/context.rs b/crates-cli/yaak-cli/src/context.rs index 47c42524..0dd0ac5c 100644 --- a/crates-cli/yaak-cli/src/context.rs +++ b/crates-cli/yaak-cli/src/context.rs @@ -18,7 +18,8 @@ impl CliContext { let blob_path = data_dir.join("blobs.sqlite"); let (query_manager, _blob_manager, _rx) = - yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize database"); + yaak_models::init_standalone(&db_path, &blob_path) + .expect("Failed to initialize database"); let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id)); diff --git a/crates-cli/yaak-cli/src/main.rs b/crates-cli/yaak-cli/src/main.rs index 5f249e21..93ac925f 100644 --- a/crates-cli/yaak-cli/src/main.rs +++ b/crates-cli/yaak-cli/src/main.rs @@ -16,8 +16,9 @@ async fn main() { let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" }; - let data_dir = - data_dir.unwrap_or_else(|| dirs::data_dir().expect("Could not determine data directory").join(app_id)); + let data_dir = data_dir.unwrap_or_else(|| { + dirs::data_dir().expect("Could not determine data directory").join(app_id) + }); let context = CliContext::initialize(data_dir, app_id).await; @@ -34,13 +35,13 @@ async fn main() { commands::request::run(&context, args, environment.as_deref(), verbose).await; 0 } - Commands::Folder(_) => { - eprintln!("Folder commands are not implemented yet"); - 1 + Commands::Folder(args) => { + commands::folder::run(&context, args); + 0 } - Commands::Environment(_) => { - eprintln!("Environment commands are not implemented yet"); - 1 + Commands::Environment(args) => { + commands::environment::run(&context, args); + 0 } }; diff --git a/crates-cli/yaak-cli/tests/common/mod.rs b/crates-cli/yaak-cli/tests/common/mod.rs new file mode 100644 index 00000000..61aab3b2 --- /dev/null +++ b/crates-cli/yaak-cli/tests/common/mod.rs @@ -0,0 +1,60 @@ +#![allow(dead_code)] + +use assert_cmd::Command; +use assert_cmd::cargo::cargo_bin_cmd; +use std::path::Path; +use yaak_models::models::{HttpRequest, Workspace}; +use yaak_models::query_manager::QueryManager; +use yaak_models::util::UpdateSource; + +pub fn cli_cmd(data_dir: &Path) -> Command { + let mut cmd = cargo_bin_cmd!("yaakcli"); + cmd.arg("--data-dir").arg(data_dir); + cmd +} + +pub fn parse_created_id(stdout: &[u8], label: &str) -> String { + String::from_utf8_lossy(stdout) + .trim() + .split_once(": ") + .map(|(_, id)| id.to_string()) + .unwrap_or_else(|| panic!("Expected id in '{label}' output")) +} + +pub fn query_manager(data_dir: &Path) -> QueryManager { + let db_path = data_dir.join("db.sqlite"); + let blob_path = data_dir.join("blobs.sqlite"); + let (query_manager, _blob_manager, _rx) = + yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize DB"); + query_manager +} + +pub fn seed_workspace(data_dir: &Path, workspace_id: &str) { + let workspace = Workspace { + id: workspace_id.to_string(), + name: "Seed Workspace".to_string(), + description: "Seeded for integration tests".to_string(), + ..Default::default() + }; + + query_manager(data_dir) + .connect() + .upsert_workspace(&workspace, &UpdateSource::Sync) + .expect("Failed to seed workspace"); +} + +pub fn seed_request(data_dir: &Path, workspace_id: &str, request_id: &str) { + let request = HttpRequest { + id: request_id.to_string(), + workspace_id: workspace_id.to_string(), + name: "Seeded Request".to_string(), + method: "GET".to_string(), + url: "https://example.com".to_string(), + ..Default::default() + }; + + query_manager(data_dir) + .connect() + .upsert_http_request(&request, &UpdateSource::Sync) + .expect("Failed to seed request"); +} diff --git a/crates-cli/yaak-cli/tests/environment_commands.rs b/crates-cli/yaak-cli/tests/environment_commands.rs new file mode 100644 index 00000000..8552a777 --- /dev/null +++ b/crates-cli/yaak-cli/tests/environment_commands.rs @@ -0,0 +1,50 @@ +mod common; + +use common::{cli_cmd, parse_created_id, query_manager, seed_workspace}; +use predicates::str::contains; +use tempfile::TempDir; + +#[test] +fn create_list_show_delete_round_trip() { + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let data_dir = temp_dir.path(); + seed_workspace(data_dir, "wk_test"); + + cli_cmd(data_dir) + .args(["environment", "list", "wk_test"]) + .assert() + .success() + .stdout(contains("Global Variables")); + + let create_assert = cli_cmd(data_dir) + .args(["environment", "create", "wk_test", "--name", "Production"]) + .assert() + .success(); + let environment_id = + parse_created_id(&create_assert.get_output().stdout, "environment create"); + + cli_cmd(data_dir) + .args(["environment", "list", "wk_test"]) + .assert() + .success() + .stdout(contains(&environment_id)) + .stdout(contains("Production")); + + cli_cmd(data_dir) + .args(["environment", "show", &environment_id]) + .assert() + .success() + .stdout(contains(format!("\"id\": \"{environment_id}\""))) + .stdout(contains("\"parentModel\": \"environment\"")); + + cli_cmd(data_dir) + .args(["environment", "delete", &environment_id, "--yes"]) + .assert() + .success() + .stdout(contains(format!("Deleted environment: {environment_id}"))); + + assert!(query_manager(data_dir) + .connect() + .get_environment(&environment_id) + .is_err()); +} diff --git a/crates-cli/yaak-cli/tests/folder_commands.rs b/crates-cli/yaak-cli/tests/folder_commands.rs new file mode 100644 index 00000000..9fd07221 --- /dev/null +++ b/crates-cli/yaak-cli/tests/folder_commands.rs @@ -0,0 +1,40 @@ +mod common; + +use common::{cli_cmd, parse_created_id, query_manager, seed_workspace}; +use predicates::str::contains; +use tempfile::TempDir; + +#[test] +fn create_list_show_delete_round_trip() { + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let data_dir = temp_dir.path(); + seed_workspace(data_dir, "wk_test"); + + let create_assert = cli_cmd(data_dir) + .args(["folder", "create", "wk_test", "--name", "Auth"]) + .assert() + .success(); + let folder_id = parse_created_id(&create_assert.get_output().stdout, "folder create"); + + cli_cmd(data_dir) + .args(["folder", "list", "wk_test"]) + .assert() + .success() + .stdout(contains(&folder_id)) + .stdout(contains("Auth")); + + cli_cmd(data_dir) + .args(["folder", "show", &folder_id]) + .assert() + .success() + .stdout(contains(format!("\"id\": \"{folder_id}\""))) + .stdout(contains("\"workspaceId\": \"wk_test\"")); + + cli_cmd(data_dir) + .args(["folder", "delete", &folder_id, "--yes"]) + .assert() + .success() + .stdout(contains(format!("Deleted folder: {folder_id}"))); + + assert!(query_manager(data_dir).connect().get_folder(&folder_id).is_err()); +} diff --git a/crates-cli/yaak-cli/tests/request_cli.rs b/crates-cli/yaak-cli/tests/request_cli.rs deleted file mode 100644 index c00c3d18..00000000 --- a/crates-cli/yaak-cli/tests/request_cli.rs +++ /dev/null @@ -1,120 +0,0 @@ -use assert_cmd::cargo::cargo_bin_cmd; -use assert_cmd::Command; -use predicates::str::contains; -use std::path::Path; -use tempfile::TempDir; -use yaak_models::models::{HttpRequest, Workspace}; -use yaak_models::util::UpdateSource; - -fn cli_cmd(data_dir: &Path) -> Command { - let mut cmd = cargo_bin_cmd!("yaakcli"); - cmd.arg("--data-dir").arg(data_dir); - cmd -} - -fn seed_workspace(data_dir: &Path, workspace_id: &str) { - let db_path = data_dir.join("db.sqlite"); - let blob_path = data_dir.join("blobs.sqlite"); - let (query_manager, _blob_manager, _rx) = - yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize DB"); - - let workspace = Workspace { - id: workspace_id.to_string(), - name: "Test Workspace".to_string(), - description: "Integration test workspace".to_string(), - ..Default::default() - }; - - query_manager - .connect() - .upsert_workspace(&workspace, &UpdateSource::Sync) - .expect("Failed to seed workspace"); -} - -fn seed_request(data_dir: &Path, workspace_id: &str, request_id: &str) { - let db_path = data_dir.join("db.sqlite"); - let blob_path = data_dir.join("blobs.sqlite"); - let (query_manager, _blob_manager, _rx) = - yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize DB"); - - let request = HttpRequest { - id: request_id.to_string(), - workspace_id: workspace_id.to_string(), - name: "Seeded Request".to_string(), - method: "GET".to_string(), - url: "https://example.com".to_string(), - ..Default::default() - }; - - query_manager - .connect() - .upsert_http_request(&request, &UpdateSource::Sync) - .expect("Failed to seed request"); -} - -#[test] -fn request_show_and_delete_yes_round_trip() { - let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let data_dir = temp_dir.path(); - seed_workspace(data_dir, "wk_test"); - - let create_assert = cli_cmd(data_dir) - .args([ - "request", - "create", - "wk_test", - "--name", - "Smoke Test", - "--url", - "https://example.com", - ]) - .assert() - .success(); - - let create_stdout = String::from_utf8_lossy(&create_assert.get_output().stdout).to_string(); - let request_id = create_stdout - .trim() - .split_once(": ") - .map(|(_, id)| id.to_string()) - .expect("Expected request id in create output"); - - cli_cmd(data_dir) - .args(["request", "show", &request_id]) - .assert() - .success() - .stdout(contains(format!("\"id\": \"{request_id}\""))) - .stdout(contains("\"workspaceId\": \"wk_test\"")); - - cli_cmd(data_dir) - .args(["request", "delete", &request_id, "--yes"]) - .assert() - .success() - .stdout(contains(format!("Deleted request: {request_id}"))); - - let db_path = data_dir.join("db.sqlite"); - let blob_path = data_dir.join("blobs.sqlite"); - let (query_manager, _blob_manager, _rx) = - yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize DB"); - assert!(query_manager.connect().get_http_request(&request_id).is_err()); -} - -#[test] -fn request_delete_without_yes_fails_in_non_interactive_mode() { - let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let data_dir = temp_dir.path(); - seed_workspace(data_dir, "wk_test"); - seed_request(data_dir, "wk_test", "rq_seed_delete_noninteractive"); - - cli_cmd(data_dir) - .args(["request", "delete", "rq_seed_delete_noninteractive"]) - .assert() - .failure() - .code(1) - .stderr(contains("Refusing to delete in non-interactive mode without --yes")); - - let db_path = data_dir.join("db.sqlite"); - let blob_path = data_dir.join("blobs.sqlite"); - let (query_manager, _blob_manager, _rx) = - yaak_models::init_standalone(&db_path, &blob_path).expect("Failed to initialize DB"); - assert!(query_manager.connect().get_http_request("rq_seed_delete_noninteractive").is_ok()); -} diff --git a/crates-cli/yaak-cli/tests/request_commands.rs b/crates-cli/yaak-cli/tests/request_commands.rs new file mode 100644 index 00000000..5f829566 --- /dev/null +++ b/crates-cli/yaak-cli/tests/request_commands.rs @@ -0,0 +1,62 @@ +mod common; + +use common::{cli_cmd, parse_created_id, query_manager, seed_request, seed_workspace}; +use predicates::str::contains; +use tempfile::TempDir; + +#[test] +fn show_and_delete_yes_round_trip() { + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let data_dir = temp_dir.path(); + seed_workspace(data_dir, "wk_test"); + + let create_assert = cli_cmd(data_dir) + .args([ + "request", + "create", + "wk_test", + "--name", + "Smoke Test", + "--url", + "https://example.com", + ]) + .assert() + .success(); + + let request_id = parse_created_id(&create_assert.get_output().stdout, "request create"); + + cli_cmd(data_dir) + .args(["request", "show", &request_id]) + .assert() + .success() + .stdout(contains(format!("\"id\": \"{request_id}\""))) + .stdout(contains("\"workspaceId\": \"wk_test\"")); + + cli_cmd(data_dir) + .args(["request", "delete", &request_id, "--yes"]) + .assert() + .success() + .stdout(contains(format!("Deleted request: {request_id}"))); + + assert!(query_manager(data_dir).connect().get_http_request(&request_id).is_err()); +} + +#[test] +fn delete_without_yes_fails_in_non_interactive_mode() { + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let data_dir = temp_dir.path(); + seed_workspace(data_dir, "wk_test"); + seed_request(data_dir, "wk_test", "rq_seed_delete_noninteractive"); + + cli_cmd(data_dir) + .args(["request", "delete", "rq_seed_delete_noninteractive"]) + .assert() + .failure() + .code(1) + .stderr(contains("Refusing to delete in non-interactive mode without --yes")); + + assert!(query_manager(data_dir) + .connect() + .get_http_request("rq_seed_delete_noninteractive") + .is_ok()); +} diff --git a/crates-cli/yaak-cli/tests/workspace_commands.rs b/crates-cli/yaak-cli/tests/workspace_commands.rs new file mode 100644 index 00000000..7a06e045 --- /dev/null +++ b/crates-cli/yaak-cli/tests/workspace_commands.rs @@ -0,0 +1,35 @@ +mod common; + +use common::{cli_cmd, parse_created_id, query_manager}; +use predicates::str::contains; +use tempfile::TempDir; + +#[test] +fn create_show_delete_round_trip() { + let temp_dir = TempDir::new().expect("Failed to create temp dir"); + let data_dir = temp_dir.path(); + + let create_assert = cli_cmd(data_dir) + .args(["workspace", "create", "--name", "WS One"]) + .assert() + .success(); + let workspace_id = parse_created_id(&create_assert.get_output().stdout, "workspace create"); + + cli_cmd(data_dir) + .args(["workspace", "show", &workspace_id]) + .assert() + .success() + .stdout(contains(format!("\"id\": \"{workspace_id}\""))) + .stdout(contains("\"name\": \"WS One\"")); + + cli_cmd(data_dir) + .args(["workspace", "delete", &workspace_id, "--yes"]) + .assert() + .success() + .stdout(contains(format!("Deleted workspace: {workspace_id}"))); + + assert!(query_manager(data_dir) + .connect() + .get_workspace(&workspace_id) + .is_err()); +}