feat: implement lists commands
This commit is contained in:
1 parent
213ed0b834
commit
c83708a727
5 files changed
+573
-7
No files matched your search
+115
-2
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, PartialEq, Parser)]
|
||||
#[command(name = "onx")]
|
||||
@@ -57,6 +58,12 @@ pub enum Command {
|
||||
#[command(subcommand)]
|
||||
command: FilesCommand,
|
||||
},
|
||||
|
||||
#[command(about = "Perform operations against the lists in the instance")]
|
||||
Lists {
|
||||
#[command(subcommand)]
|
||||
command: ListsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Subcommand)]
|
||||
@@ -244,6 +251,7 @@ pub enum FilesCommand {
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
|
||||
#[command(about = "Upload a file to a field on a record")]
|
||||
Upload {
|
||||
#[arg(long = "record")]
|
||||
#[arg(short = 'r')]
|
||||
@@ -284,6 +292,7 @@ pub enum FilesCommand {
|
||||
modified_date: Option<String>,
|
||||
},
|
||||
|
||||
#[command(about = "Delete a file from a field on a record")]
|
||||
Delete {
|
||||
#[arg(long = "record")]
|
||||
#[arg(short = 'r')]
|
||||
@@ -302,6 +311,33 @@ pub enum FilesCommand {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Subcommand)]
|
||||
pub enum ListsCommand {
|
||||
#[command(about = "Add or update an item for a specific list")]
|
||||
SaveItem {
|
||||
#[arg(long = "list")]
|
||||
#[arg(short = 'l')]
|
||||
#[arg(help = "List id for list where the item should be saved")]
|
||||
list_id: i32,
|
||||
|
||||
#[command(flatten)]
|
||||
body: BodySource,
|
||||
},
|
||||
|
||||
#[command(about = "Delete an item from a specific list")]
|
||||
DeleteItem {
|
||||
#[arg(long = "list")]
|
||||
#[arg(short = 'l')]
|
||||
#[arg(help = "List id for list where the item should be saved")]
|
||||
list_id: i32,
|
||||
|
||||
#[arg(long = "item")]
|
||||
#[arg(short = 'i')]
|
||||
#[arg(help = "Id of the item that should be deleted")]
|
||||
item_id: Uuid,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Args, Default)]
|
||||
pub struct PagingArgs {
|
||||
#[arg(long)]
|
||||
@@ -994,8 +1030,22 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_cli_from_when_called_with_files_upload_it_should_return_cli() {
|
||||
let result = parse_cli_from([
|
||||
"test", "files", "upload", "-r", "1", "-f", "2", "-i", "doc.pdf", "-n", "renamed.pdf",
|
||||
"-t", "application/pdf", "--notes", "test notes", "--modified-date",
|
||||
"test",
|
||||
"files",
|
||||
"upload",
|
||||
"-r",
|
||||
"1",
|
||||
"-f",
|
||||
"2",
|
||||
"-i",
|
||||
"doc.pdf",
|
||||
"-n",
|
||||
"renamed.pdf",
|
||||
"-t",
|
||||
"application/pdf",
|
||||
"--notes",
|
||||
"test notes",
|
||||
"--modified-date",
|
||||
"2026-01-01T00:00:00Z",
|
||||
]);
|
||||
|
||||
@@ -1052,4 +1102,67 @@ mod tests {
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(err.code, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cli_from_when_called_with_lists_save_item_it_should_return_cli() {
|
||||
let result = parse_cli_from([
|
||||
"test", "lists", "save-item", "-l", "1", "-j", r#"{"name":"Item 1"}"#,
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(Cli {
|
||||
api_key: None,
|
||||
base_url: None,
|
||||
pretty: false,
|
||||
command: Command::Lists {
|
||||
command: ListsCommand::SaveItem {
|
||||
list_id: 1,
|
||||
body: BodySource {
|
||||
json: Some(r#"{"name":"Item 1"}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cli_from_when_called_with_lists_delete_item_it_should_return_cli() {
|
||||
let result = parse_cli_from([
|
||||
"test",
|
||||
"lists",
|
||||
"delete-item",
|
||||
"-l",
|
||||
"10",
|
||||
"-i",
|
||||
"d3b07384-d113-40e1-a20c-55c3c0a4e70e",
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(Cli {
|
||||
api_key: None,
|
||||
base_url: None,
|
||||
pretty: false,
|
||||
command: Command::Lists {
|
||||
command: ListsCommand::DeleteItem {
|
||||
list_id: 10,
|
||||
item_id: Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap(),
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cli_from_when_called_with_lists_without_subcommand_it_should_return_usage_error() {
|
||||
let result = parse_cli_from(["test", "lists"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(err.code, 2);
|
||||
}
|
||||
}
|
||||
+69
-2
@@ -1,9 +1,10 @@
|
||||
use onspring::{
|
||||
App, BatchDeleteRecordsRequest, BatchGetRecordsRequest, CollectionResponse,
|
||||
CreatedWithIdResponse, DataFormat, Field, FileInfo, FileResponse, OnspringClient, OnspringError,
|
||||
PagedResponse, PagingRequest, QueryRecordsRequest, Record, SaveFileRequest, SaveRecordRequest,
|
||||
SaveRecordResponse,
|
||||
PagedResponse, PagingRequest, QueryRecordsRequest, Record, SaveFileRequest, SaveListItemRequest,
|
||||
SaveListItemResponse, SaveRecordRequest, SaveRecordResponse,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub trait OnspringRunner {
|
||||
fn ping(&self) -> impl std::future::Future<Output = Result<(), OnspringError>> + Send;
|
||||
@@ -107,6 +108,18 @@ pub trait OnspringRunner {
|
||||
field_id: i32,
|
||||
file_id: i32,
|
||||
) -> impl std::future::Future<Output = Result<(), OnspringError>> + Send;
|
||||
|
||||
fn save_list_item(
|
||||
&self,
|
||||
list_id: i32,
|
||||
request: SaveListItemRequest,
|
||||
) -> impl std::future::Future<Output = Result<SaveListItemResponse, OnspringError>> + Send;
|
||||
|
||||
fn delete_list_item(
|
||||
&self,
|
||||
list_id: i32,
|
||||
item_id: Uuid,
|
||||
) -> impl std::future::Future<Output = Result<(), OnspringError>> + Send;
|
||||
}
|
||||
|
||||
impl OnspringRunner for OnspringClient {
|
||||
@@ -231,6 +244,18 @@ impl OnspringRunner for OnspringClient {
|
||||
) -> Result<(), OnspringError> {
|
||||
self.delete_file(record_id, field_id, file_id).await
|
||||
}
|
||||
|
||||
async fn save_list_item(
|
||||
&self,
|
||||
list_id: i32,
|
||||
request: SaveListItemRequest,
|
||||
) -> Result<SaveListItemResponse, OnspringError> {
|
||||
self.save_list_item(list_id, request).await
|
||||
}
|
||||
|
||||
async fn delete_list_item(&self, list_id: i32, item_id: Uuid) -> Result<(), OnspringError> {
|
||||
self.delete_list_item(list_id, item_id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -299,6 +324,15 @@ pub mod testing {
|
||||
pub delete_file_record_id: Mutex<Option<i32>>,
|
||||
pub delete_file_field_id: Mutex<Option<i32>>,
|
||||
pub delete_file_file_id: Mutex<Option<i32>>,
|
||||
|
||||
pub save_list_item_result: Result<SaveListItemResponse, OnspringError>,
|
||||
pub delete_list_item_result: Result<(), OnspringError>,
|
||||
|
||||
pub save_list_item_list_id: Mutex<Option<i32>>,
|
||||
pub save_list_item_request: Mutex<Option<SaveListItemRequest>>,
|
||||
|
||||
pub delete_list_item_list_id: Mutex<Option<i32>>,
|
||||
pub delete_list_item_item_id: Mutex<Option<Uuid>>,
|
||||
}
|
||||
|
||||
impl Default for MockClient {
|
||||
@@ -434,6 +468,17 @@ pub mod testing {
|
||||
delete_file_record_id: Mutex::new(None),
|
||||
delete_file_field_id: Mutex::new(None),
|
||||
delete_file_file_id: Mutex::new(None),
|
||||
|
||||
save_list_item_result: Ok(SaveListItemResponse {
|
||||
id: Uuid::nil(),
|
||||
}),
|
||||
delete_list_item_result: Ok(()),
|
||||
|
||||
save_list_item_list_id: Mutex::new(None),
|
||||
save_list_item_request: Mutex::new(None),
|
||||
|
||||
delete_list_item_list_id: Mutex::new(None),
|
||||
delete_list_item_item_id: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,5 +709,27 @@ pub mod testing {
|
||||
Err(err) => Err(clone_onspring_error(err)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_list_item(
|
||||
&self,
|
||||
list_id: i32,
|
||||
request: SaveListItemRequest,
|
||||
) -> Result<SaveListItemResponse, OnspringError> {
|
||||
*self.save_list_item_list_id.lock().unwrap() = Some(list_id);
|
||||
*self.save_list_item_request.lock().unwrap() = Some(request);
|
||||
match &self.save_list_item_result {
|
||||
Ok(res) => Ok(res.clone()),
|
||||
Err(err) => Err(clone_onspring_error(err)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_list_item(&self, list_id: i32, item_id: Uuid) -> Result<(), OnspringError> {
|
||||
*self.delete_list_item_list_id.lock().unwrap() = Some(list_id);
|
||||
*self.delete_list_item_item_id.lock().unwrap() = Some(item_id);
|
||||
match &self.delete_list_item_result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => Err(clone_onspring_error(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use onspring::SaveListItemRequest;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::cli::ListsCommand;
|
||||
use crate::client::OnspringRunner;
|
||||
use crate::error::CliResult;
|
||||
use crate::output::{SaveListItemResponseOutput, SuccessResponse, write_json};
|
||||
use crate::validation::{parse_body, validate_positive_i32};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveListItemInput {
|
||||
pub id: Option<Uuid>,
|
||||
pub name: String,
|
||||
pub numeric_value: Option<f64>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
impl From<SaveListItemInput> for SaveListItemRequest {
|
||||
fn from(value: SaveListItemInput) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
numeric_value: value.numeric_value,
|
||||
color: value.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle<C: OnspringRunner, W: Write>(
|
||||
command: &ListsCommand,
|
||||
client: &C,
|
||||
writer: &mut W,
|
||||
pretty: bool,
|
||||
) -> CliResult<()> {
|
||||
handle_with_reader(command, client, writer, std::io::stdin(), pretty).await
|
||||
}
|
||||
|
||||
pub async fn handle_with_reader<C: OnspringRunner, W: Write, R: Read>(
|
||||
command: &ListsCommand,
|
||||
client: &C,
|
||||
writer: &mut W,
|
||||
reader: R,
|
||||
pretty: bool,
|
||||
) -> CliResult<()> {
|
||||
match command {
|
||||
ListsCommand::SaveItem { list_id, body } => {
|
||||
validate_positive_i32(*list_id, "list_id")?;
|
||||
let input: SaveListItemInput = parse_body(body, reader)?;
|
||||
let request: SaveListItemRequest = input.into();
|
||||
let response = client.save_list_item(*list_id, request).await?;
|
||||
let output: SaveListItemResponseOutput = response.into();
|
||||
write_json(writer, &output, pretty)?;
|
||||
}
|
||||
ListsCommand::DeleteItem { list_id, item_id } => {
|
||||
validate_positive_i32(*list_id, "list_id")?;
|
||||
client.delete_list_item(*list_id, *item_id).await?;
|
||||
write_json(writer, &SuccessResponse { ok: true }, pretty)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use onspring::{OnspringError, SaveListItemResponse};
|
||||
|
||||
use crate::cli::BodySource;
|
||||
use crate::client::testing::MockClient;
|
||||
use crate::error::CliError;
|
||||
|
||||
// --- SaveItem tests ---
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_succeeds_it_should_write_save_response() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
save_list_item_result: Ok(SaveListItemResponse { id: item_uuid }),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 1,
|
||||
body: BodySource {
|
||||
json: Some(
|
||||
r##"{"id":"d3b07384-d113-40e1-a20c-55c3c0a4e70e","name":"Item 1","numericValue":10.5,"color":"#ffffff"}"##
|
||||
.to_string(),
|
||||
),
|
||||
file: None,
|
||||
stdin: false,
|
||||
},
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let written = String::from_utf8(buffer).unwrap();
|
||||
assert_eq!(
|
||||
written.trim(),
|
||||
r#"{"id":"d3b07384-d113-40e1-a20c-55c3c0a4e70e"}"#
|
||||
);
|
||||
assert_eq!(*mock_client.save_list_item_list_id.lock().unwrap(), Some(1));
|
||||
let req = mock_client
|
||||
.save_list_item_request
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap();
|
||||
assert_eq!(req.id, Some(item_uuid));
|
||||
assert_eq!(req.name, "Item 1");
|
||||
assert_eq!(req.numeric_value, Some(10.5));
|
||||
assert_eq!(req.color, Some("#ffffff".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_succeeds_pretty_it_should_write_pretty_json() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
save_list_item_result: Ok(SaveListItemResponse { id: item_uuid }),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 1,
|
||||
body: BodySource {
|
||||
json: Some(r#"{"name":"New Item"}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
},
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, true).await;
|
||||
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let written = String::from_utf8(buffer).unwrap();
|
||||
let expected = concat!(
|
||||
"{\n",
|
||||
" \"id\": \"d3b07384-d113-40e1-a20c-55c3c0a4e70e\"\n",
|
||||
"}"
|
||||
);
|
||||
assert_eq!(written.trim(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_with_invalid_list_id_it_should_return_usage_error() {
|
||||
let mock_client = MockClient::default();
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 0,
|
||||
body: BodySource {
|
||||
json: Some(r#"{"name":"Item"}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
},
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::usage("list_id must be greater than 0."))
|
||||
);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_has_missing_body_it_should_return_usage_error() {
|
||||
let mock_client = MockClient::default();
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 1,
|
||||
body: BodySource::default(),
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::usage(
|
||||
"A request body is required. Provide --json, --file, or --stdin."
|
||||
))
|
||||
);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_reads_from_stdin() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
save_list_item_result: Ok(SaveListItemResponse { id: item_uuid }),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 5,
|
||||
body: BodySource {
|
||||
json: None,
|
||||
file: None,
|
||||
stdin: true,
|
||||
},
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
let stdin_data = r#"{"name":"From Stdin","numericValue":2.0}"#;
|
||||
|
||||
let result = handle_with_reader(
|
||||
&command,
|
||||
&mock_client,
|
||||
&mut buffer,
|
||||
stdin_data.as_bytes(),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result, Ok(()));
|
||||
let written = String::from_utf8(buffer).unwrap();
|
||||
assert_eq!(
|
||||
written.trim(),
|
||||
r#"{"id":"d3b07384-d113-40e1-a20c-55c3c0a4e70e"}"#
|
||||
);
|
||||
let req = mock_client
|
||||
.save_list_item_request
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap();
|
||||
assert_eq!(req.name, "From Stdin");
|
||||
assert_eq!(req.numeric_value, Some(2.0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_save_item_fails_it_should_return_mapped_cli_error() {
|
||||
let mock_client = MockClient {
|
||||
save_list_item_result: Err(OnspringError::Api {
|
||||
status_code: 400,
|
||||
message: "Invalid list item".to_string(),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::SaveItem {
|
||||
list_id: 1,
|
||||
body: BodySource {
|
||||
json: Some(r#"{"name":"Item"}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
},
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::runtime(
|
||||
"API request failed (400): Invalid list item"
|
||||
))
|
||||
);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
// --- DeleteItem tests ---
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_delete_item_succeeds_it_should_write_success_response() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
delete_list_item_result: Ok(()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::DeleteItem {
|
||||
list_id: 1,
|
||||
item_id: item_uuid,
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let written = String::from_utf8(buffer).unwrap();
|
||||
assert_eq!(written.trim(), r#"{"ok":true}"#);
|
||||
assert_eq!(*mock_client.delete_list_item_list_id.lock().unwrap(), Some(1));
|
||||
assert_eq!(
|
||||
*mock_client.delete_list_item_item_id.lock().unwrap(),
|
||||
Some(item_uuid)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_delete_item_succeeds_pretty_it_should_write_pretty_json() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
delete_list_item_result: Ok(()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::DeleteItem {
|
||||
list_id: 1,
|
||||
item_id: item_uuid,
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, true).await;
|
||||
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let written = String::from_utf8(buffer).unwrap();
|
||||
let expected = concat!("{\n", " \"ok\": true\n", "}");
|
||||
assert_eq!(written.trim(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_delete_item_with_invalid_list_id_it_should_return_usage_error() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient::default();
|
||||
|
||||
let command = ListsCommand::DeleteItem {
|
||||
list_id: 0,
|
||||
item_id: item_uuid,
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::usage("list_id must be greater than 0."))
|
||||
);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_when_delete_item_fails_it_should_return_mapped_cli_error() {
|
||||
let item_uuid = Uuid::parse_str("d3b07384-d113-40e1-a20c-55c3c0a4e70e").unwrap();
|
||||
let mock_client = MockClient {
|
||||
delete_list_item_result: Err(OnspringError::Api {
|
||||
status_code: 404,
|
||||
message: "List item not found".to_string(),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let command = ListsCommand::DeleteItem {
|
||||
list_id: 1,
|
||||
item_id: item_uuid,
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let result = handle(&command, &mock_client, &mut buffer, false).await;
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::runtime(
|
||||
"API request failed (404): List item not found"
|
||||
))
|
||||
);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,8 +1,9 @@
|
||||
pub mod apps;
|
||||
pub mod fields;
|
||||
pub mod files;
|
||||
pub mod lists;
|
||||
pub mod ping;
|
||||
pub mod records;
|
||||
pub mod files;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
@@ -21,5 +22,6 @@ pub async fn handle<C: OnspringRunner, W: Write>(
|
||||
Command::Fields { command } => fields::handle(command, client, writer, cli.pretty).await,
|
||||
Command::Records { command } => records::handle(command, client, writer, cli.pretty).await,
|
||||
Command::Files { command } => files::handle(command, client, writer, cli.pretty).await,
|
||||
Command::Lists { command } => lists::handle(command, client, writer, cli.pretty).await,
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -3,8 +3,8 @@ use std::io::Write;
|
||||
use chrono::{DateTime, Utc};
|
||||
use onspring::{
|
||||
App, CollectionResponse, CreatedWithIdResponse, Field, FileInfo, FormulaOutputType,
|
||||
ListFieldValue, Multiplicity, PagedResponse, Record, RecordFieldValue, SaveRecordResponse,
|
||||
ValueType,
|
||||
ListFieldValue, Multiplicity, PagedResponse, Record, RecordFieldValue, SaveListItemResponse,
|
||||
SaveRecordResponse, ValueType,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
@@ -251,6 +251,18 @@ impl From<CreatedWithIdResponse> for CreatedWithIdResponseOutput {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SaveListItemResponseOutput {
|
||||
pub id: Uuid,
|
||||
}
|
||||
|
||||
impl From<SaveListItemResponse> for SaveListItemResponseOutput {
|
||||
fn from(value: SaveListItemResponse) -> Self {
|
||||
Self { id: value.id }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_json<T: Serialize>(value: &T, pretty: bool) -> CliResult<String> {
|
||||
if pretty {
|
||||
serde_json::to_string_pretty(value).map_err(|_| CliError::runtime("Failed to serialize output"))
|
||||
|
||||
Reference in new issue
Block a user