mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
add workspace/folder/environment commands and reorganize cli tests
This commit is contained in:
@@ -145,7 +145,7 @@ Existing behavior stays the same, just reorganized. Remove the `get` command.
|
|||||||
|
|
||||||
### Phase 2: Add missing CRUD commands
|
### 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 <id>`
|
1. `workspace show <id>`
|
||||||
2. `workspace create --name <name>` (and `--json`)
|
2. `workspace create --name <name>` (and `--json`)
|
||||||
|
|||||||
69
crates-cli/yaak-cli/README.md
Normal file
69
crates-cli/yaak-cli/README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# yaak-cli
|
||||||
|
|
||||||
|
Command-line interface for Yaak.
|
||||||
|
|
||||||
|
## Command Overview
|
||||||
|
|
||||||
|
Current top-level commands:
|
||||||
|
|
||||||
|
```text
|
||||||
|
yaakcli send <request_id>
|
||||||
|
yaakcli workspace list
|
||||||
|
yaakcli workspace show <workspace_id>
|
||||||
|
yaakcli workspace create --name <name>
|
||||||
|
yaakcli workspace delete <workspace_id> [--yes]
|
||||||
|
yaakcli request list <workspace_id>
|
||||||
|
yaakcli request show <request_id>
|
||||||
|
yaakcli request send <request_id>
|
||||||
|
yaakcli request create <workspace_id> --name <name> --url <url> [--method GET]
|
||||||
|
yaakcli request delete <request_id> [--yes]
|
||||||
|
yaakcli folder list <workspace_id>
|
||||||
|
yaakcli folder show <folder_id>
|
||||||
|
yaakcli folder create <workspace_id> --name <name>
|
||||||
|
yaakcli folder delete <folder_id> [--yes]
|
||||||
|
yaakcli environment list <workspace_id>
|
||||||
|
yaakcli environment show <environment_id>
|
||||||
|
yaakcli environment create <workspace_id> --name <name>
|
||||||
|
yaakcli environment delete <environment_id> [--yes]
|
||||||
|
```
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
|
||||||
|
- `--data-dir <path>`: use a custom data directory
|
||||||
|
- `-e, --environment <id>`: 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
|
||||||
|
```
|
||||||
@@ -32,12 +32,10 @@ pub enum Commands {
|
|||||||
/// Request commands
|
/// Request commands
|
||||||
Request(RequestArgs),
|
Request(RequestArgs),
|
||||||
|
|
||||||
/// Folder commands (coming soon)
|
/// Folder commands
|
||||||
#[command(hide = true)]
|
|
||||||
Folder(FolderArgs),
|
Folder(FolderArgs),
|
||||||
|
|
||||||
/// Environment commands (coming soon)
|
/// Environment commands
|
||||||
#[command(hide = true)]
|
|
||||||
Environment(EnvironmentArgs),
|
Environment(EnvironmentArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +55,29 @@ pub struct WorkspaceArgs {
|
|||||||
pub enum WorkspaceCommands {
|
pub enum WorkspaceCommands {
|
||||||
/// List all workspaces
|
/// List all workspaces
|
||||||
List,
|
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)]
|
#[derive(Args)]
|
||||||
@@ -115,7 +136,83 @@ pub enum RequestCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[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)]
|
#[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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
16
crates-cli/yaak-cli/src/commands/confirm.rs
Normal file
16
crates-cli/yaak-cli/src/commands/confirm.rs
Normal file
@@ -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")
|
||||||
|
}
|
||||||
64
crates-cli/yaak-cli/src/commands/environment.rs
Normal file
64
crates-cli/yaak-cli/src/commands/environment.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
54
crates-cli/yaak-cli/src/commands/folder.rs
Normal file
54
crates-cli/yaak-cli/src/commands/folder.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
pub mod confirm;
|
||||||
|
pub mod environment;
|
||||||
|
pub mod folder;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod send;
|
pub mod send;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::cli::{RequestArgs, RequestCommands};
|
use crate::cli::{RequestArgs, RequestCommands};
|
||||||
|
use crate::commands::confirm::confirm_delete;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{self, IsTerminal, Write};
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use yaak_http::path_placeholders::apply_path_placeholders;
|
use yaak_http::path_placeholders::apply_path_placeholders;
|
||||||
use yaak_http::sender::{HttpSender, ReqwestSender};
|
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) {
|
fn list(ctx: &CliContext, workspace_id: &str) {
|
||||||
let requests = ctx
|
let requests = ctx.db().list_http_requests(workspace_id).expect("Failed to list requests");
|
||||||
.db()
|
|
||||||
.list_http_requests(workspace_id)
|
|
||||||
.expect("Failed to list requests");
|
|
||||||
if requests.is_empty() {
|
if requests.is_empty() {
|
||||||
println!("No requests found in workspace {}", workspace_id);
|
println!("No requests found in workspace {}", workspace_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -61,16 +58,13 @@ fn create(ctx: &CliContext, workspace_id: String, name: String, method: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(ctx: &CliContext, request_id: &str) {
|
fn show(ctx: &CliContext, request_id: &str) {
|
||||||
let request = ctx
|
let request = ctx.db().get_http_request(request_id).expect("Failed to get request");
|
||||||
.db()
|
|
||||||
.get_http_request(request_id)
|
|
||||||
.expect("Failed to get request");
|
|
||||||
let output = serde_json::to_string_pretty(&request).expect("Failed to serialize request");
|
let output = serde_json::to_string_pretty(&request).expect("Failed to serialize request");
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(ctx: &CliContext, request_id: &str, yes: bool) {
|
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");
|
println!("Aborted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,21 +76,6 @@ fn delete(ctx: &CliContext, request_id: &str, yes: bool) {
|
|||||||
println!("Deleted request: {}", deleted.id);
|
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`.
|
/// Send a request by ID and print response in the same format as legacy `send`.
|
||||||
pub async fn send_request_by_id(
|
pub async fn send_request_by_id(
|
||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
@@ -104,18 +83,11 @@ pub async fn send_request_by_id(
|
|||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) {
|
) {
|
||||||
let request = ctx
|
let request = ctx.db().get_http_request(request_id).expect("Failed to get request");
|
||||||
.db()
|
|
||||||
.get_http_request(request_id)
|
|
||||||
.expect("Failed to get request");
|
|
||||||
|
|
||||||
let environment_chain = ctx
|
let environment_chain = ctx
|
||||||
.db()
|
.db()
|
||||||
.resolve_environments(
|
.resolve_environments(&request.workspace_id, request.folder_id.as_deref(), environment)
|
||||||
&request.workspace_id,
|
|
||||||
request.folder_id.as_deref(),
|
|
||||||
environment,
|
|
||||||
)
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let plugin_context = PluginContext::new(None, Some(request.workspace_id.clone()));
|
let plugin_context = PluginContext::new(None, Some(request.workspace_id.clone()));
|
||||||
@@ -169,11 +141,7 @@ pub async fn send_request_by_id(
|
|||||||
if verbose {
|
if verbose {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
println!(
|
println!("HTTP {} {}", response.status, response.status_reason.as_deref().unwrap_or(""));
|
||||||
"HTTP {} {}",
|
|
||||||
response.status,
|
|
||||||
response.status_reason.as_deref().unwrap_or("")
|
|
||||||
);
|
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
for (name, value) in &response.headers {
|
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);
|
let (url, url_parameters) = apply_path_placeholders(&url, &url_parameters);
|
||||||
|
|
||||||
Ok(HttpRequest {
|
Ok(HttpRequest { url, url_parameters, headers, body, authentication, ..request.to_owned() })
|
||||||
url,
|
|
||||||
url_parameters,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
authentication,
|
|
||||||
..request.to_owned()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
use crate::cli::{WorkspaceArgs, WorkspaceCommands};
|
use crate::cli::{WorkspaceArgs, WorkspaceCommands};
|
||||||
|
use crate::commands::confirm::confirm_delete;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
use yaak_models::models::Workspace;
|
||||||
|
use yaak_models::util::UpdateSource;
|
||||||
|
|
||||||
pub fn run(ctx: &CliContext, args: WorkspaceArgs) {
|
pub fn run(ctx: &CliContext, args: WorkspaceArgs) {
|
||||||
match args.command {
|
match args.command {
|
||||||
WorkspaceCommands::List => list(ctx),
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ impl CliContext {
|
|||||||
let blob_path = data_dir.join("blobs.sqlite");
|
let blob_path = data_dir.join("blobs.sqlite");
|
||||||
|
|
||||||
let (query_manager, _blob_manager, _rx) =
|
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));
|
let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id));
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ async fn main() {
|
|||||||
|
|
||||||
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };
|
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };
|
||||||
|
|
||||||
let data_dir =
|
let data_dir = data_dir.unwrap_or_else(|| {
|
||||||
data_dir.unwrap_or_else(|| dirs::data_dir().expect("Could not determine data directory").join(app_id));
|
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
||||||
|
});
|
||||||
|
|
||||||
let context = CliContext::initialize(data_dir, app_id).await;
|
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;
|
commands::request::run(&context, args, environment.as_deref(), verbose).await;
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Commands::Folder(_) => {
|
Commands::Folder(args) => {
|
||||||
eprintln!("Folder commands are not implemented yet");
|
commands::folder::run(&context, args);
|
||||||
1
|
0
|
||||||
}
|
}
|
||||||
Commands::Environment(_) => {
|
Commands::Environment(args) => {
|
||||||
eprintln!("Environment commands are not implemented yet");
|
commands::environment::run(&context, args);
|
||||||
1
|
0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
60
crates-cli/yaak-cli/tests/common/mod.rs
Normal file
60
crates-cli/yaak-cli/tests/common/mod.rs
Normal file
@@ -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");
|
||||||
|
}
|
||||||
50
crates-cli/yaak-cli/tests/environment_commands.rs
Normal file
50
crates-cli/yaak-cli/tests/environment_commands.rs
Normal file
@@ -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());
|
||||||
|
}
|
||||||
40
crates-cli/yaak-cli/tests/folder_commands.rs
Normal file
40
crates-cli/yaak-cli/tests/folder_commands.rs
Normal file
@@ -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());
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
62
crates-cli/yaak-cli/tests/request_commands.rs
Normal file
62
crates-cli/yaak-cli/tests/request_commands.rs
Normal file
@@ -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());
|
||||||
|
}
|
||||||
35
crates-cli/yaak-cli/tests/workspace_commands.rs
Normal file
35
crates-cli/yaak-cli/tests/workspace_commands.rs
Normal file
@@ -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());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user