diff --git a/Cargo.toml b/Cargo.toml index 7a2c24f..b3780e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,13 @@ [package] -name = "onspring" +name = "onspring-api-sdk-rust" version = "0.0.0" edition = "2021" description = "Rust SDK for the Onspring API" license = "MIT" +[lib] +name = "onspring" + [dependencies] bytes = "1" reqwest = { version = "0.12", features = ["json", "multipart", "stream"] } diff --git a/examples/apps.rs b/examples/apps.rs new file mode 100644 index 0000000..f677e0e --- /dev/null +++ b/examples/apps.rs @@ -0,0 +1,51 @@ +use onspring::{OnspringClient, PagingRequest}; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // List apps with paging + let paging = PagingRequest { + page_number: 1, + page_size: 10, + }; + let response = client.list_apps(Some(paging)).await?; + println!("Total apps: {:?}", response.total_records); + + if let Some(items) = &response.items { + for app in items { + println!( + " App: {} (id: {})", + app.name.as_deref().unwrap_or("N/A"), + app.id + ); + } + + // Get a single app by ID + if let Some(first) = items.first() { + let app = client.get_app(first.id).await?; + println!( + "\nFetched app: {} (id: {})", + app.name.as_deref().unwrap_or("N/A"), + app.id + ); + } + + // Batch get apps + let ids: Vec = items.iter().take(3).map(|a| a.id).collect(); + if !ids.is_empty() { + let batch = client.batch_get_apps(&ids).await?; + println!( + "\nBatch fetched {} apps", + batch.items.map(|i| i.len()).unwrap_or(0) + ); + } + } + + Ok(()) +} diff --git a/examples/fields.rs b/examples/fields.rs new file mode 100644 index 0000000..711876b --- /dev/null +++ b/examples/fields.rs @@ -0,0 +1,56 @@ +use onspring::{OnspringClient, PagingRequest}; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + let app_id: i32 = std::env::var("TEST_APP_ID") + .expect("TEST_APP_ID is required") + .parse() + .expect("TEST_APP_ID must be an integer"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // List fields for an app + let paging = PagingRequest { + page_number: 1, + page_size: 10, + }; + let response = client.list_fields(app_id, Some(paging)).await?; + println!("Total fields: {:?}", response.total_records); + + if let Some(items) = &response.items { + for field in items { + println!( + " Field: {} (id: {}, type: {:?})", + field.name.as_deref().unwrap_or("N/A"), + field.id, + field.field_type.as_deref().unwrap_or("N/A"), + ); + } + + // Get a single field + if let Some(first) = items.first() { + let field = client.get_field(first.id).await?; + println!( + "\nFetched field: {} (id: {})", + field.name.as_deref().unwrap_or("N/A"), + field.id + ); + } + + // Batch get fields + let ids: Vec = items.iter().take(3).map(|f| f.id).collect(); + if !ids.is_empty() { + let batch = client.batch_get_fields(&ids).await?; + println!( + "\nBatch fetched {} fields", + batch.items.map(|i| i.len()).unwrap_or(0) + ); + } + } + + Ok(()) +} diff --git a/examples/files.rs b/examples/files.rs new file mode 100644 index 0000000..c947335 --- /dev/null +++ b/examples/files.rs @@ -0,0 +1,58 @@ +use onspring::{OnspringClient, SaveFileRequest}; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + let record_id: i32 = std::env::var("TEST_RECORD") + .expect("TEST_RECORD is required") + .parse() + .expect("TEST_RECORD must be an integer"); + let field_id: i32 = std::env::var("TEST_ATTACHMENT_FIELD") + .expect("TEST_ATTACHMENT_FIELD is required") + .parse() + .expect("TEST_ATTACHMENT_FIELD must be an integer"); + let file_id: i32 = std::env::var("TEST_ATTACHMENT") + .expect("TEST_ATTACHMENT is required") + .parse() + .expect("TEST_ATTACHMENT must be an integer"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // Get file info + let info = client.get_file_info(record_id, field_id, file_id).await?; + println!("File info:"); + println!(" Name: {:?}", info.name); + println!(" Content type: {:?}", info.content_type); + println!(" Created: {:?}", info.created_date); + println!(" Owner: {:?}", info.owner); + + // Download file content + let file = client.get_file(record_id, field_id, file_id).await?; + println!("\nDownloaded file:"); + println!(" File name: {:?}", file.file_name); + println!(" Content type: {:?}", file.content_type); + println!(" Size: {} bytes", file.data.len()); + + // Upload a file + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("Uploaded from Rust SDK example".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "example-upload.txt".to_string(), + file_data: b"Hello from the Rust SDK!".to_vec(), + content_type: "text/plain".to_string(), + }; + + let uploaded = client.upload_file(request).await?; + println!("\nUploaded file with ID: {}", uploaded.id); + + // Delete the uploaded file + client.delete_file(record_id, field_id, uploaded.id).await?; + println!("Deleted file {}", uploaded.id); + + Ok(()) +} diff --git a/examples/lists.rs b/examples/lists.rs new file mode 100644 index 0000000..8d95800 --- /dev/null +++ b/examples/lists.rs @@ -0,0 +1,45 @@ +use onspring::{OnspringClient, SaveListItemRequest}; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + let list_id: i32 = std::env::var("TEST_LIST_ID") + .expect("TEST_LIST_ID is required") + .parse() + .expect("TEST_LIST_ID must be an integer"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // Add a list item + let name = format!("rust_sdk_example_{}", chrono::Utc::now().timestamp_millis()); + let request = SaveListItemRequest { + id: None, + name: name.clone(), + numeric_value: Some(42.0), + color: Some("#FF5733".to_string()), + }; + + let response = client.save_list_item(list_id, request).await?; + println!("Added list item '{}' with ID: {}", name, response.id); + + // Update the list item + let update_name = format!("updated_{}", name); + let update_request = SaveListItemRequest { + id: Some(response.id), + name: update_name.clone(), + numeric_value: Some(99.0), + color: Some("#00FF00".to_string()), + }; + + client.save_list_item(list_id, update_request).await?; + println!("Updated list item to '{}'", update_name); + + // Delete the list item + client.delete_list_item(list_id, response.id).await?; + println!("Deleted list item {}", response.id); + + Ok(()) +} diff --git a/examples/ping.rs b/examples/ping.rs new file mode 100644 index 0000000..4ccf528 --- /dev/null +++ b/examples/ping.rs @@ -0,0 +1,16 @@ +use onspring::OnspringClient; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + client.ping().await?; + println!("Connected to the Onspring API successfully."); + + Ok(()) +} diff --git a/examples/records.rs b/examples/records.rs new file mode 100644 index 0000000..95af31a --- /dev/null +++ b/examples/records.rs @@ -0,0 +1,84 @@ +use onspring::{OnspringClient, PagingRequest, QueryRecordsRequest, SaveRecordRequest}; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + let app_id: i32 = std::env::var("TEST_SURVEY_ID") + .expect("TEST_SURVEY_ID is required") + .parse() + .expect("TEST_SURVEY_ID must be an integer"); + let field_id = std::env::var("TEST_TEXT_FIELD").expect("TEST_TEXT_FIELD is required"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // List records + let paging = PagingRequest { + page_number: 1, + page_size: 5, + }; + let response = client + .list_records(app_id, Some(paging), None, None) + .await?; + println!("Total records: {:?}", response.total_records); + + if let Some(items) = &response.items { + for record in items { + println!(" Record ID: {}", record.record_id); + } + } + + // Create a record + let mut fields = HashMap::new(); + fields.insert(field_id.clone(), serde_json::json!("Hello from Rust SDK")); + + let request = SaveRecordRequest { + app_id, + record_id: None, + fields, + }; + + let saved = client.save_record(request).await?; + println!("\nCreated record with ID: {}", saved.id); + + // Get the record back + let record = client.get_record(app_id, saved.id, None, None).await?; + println!( + "Fetched record: app_id={}, record_id={}", + record.app_id, record.record_id + ); + + // Update the record + let mut fields = HashMap::new(); + fields.insert(field_id, serde_json::json!("Updated from Rust SDK")); + + let update = SaveRecordRequest { + app_id, + record_id: Some(saved.id), + fields, + }; + client.save_record(update).await?; + println!("Updated record {}", saved.id); + + // Query records + let query = QueryRecordsRequest { + app_id, + filter: format!( + "{} contains 'Rust'", + std::env::var("TEST_TEXT_FIELD").unwrap() + ), + field_ids: None, + data_format: None, + }; + let query_result = client.query_records(query, None).await?; + println!("\nQuery found {:?} records", query_result.total_records); + + // Delete the record + client.delete_record(app_id, saved.id).await?; + println!("Deleted record {}", saved.id); + + Ok(()) +} diff --git a/examples/reports.rs b/examples/reports.rs new file mode 100644 index 0000000..3346689 --- /dev/null +++ b/examples/reports.rs @@ -0,0 +1,56 @@ +use onspring::{OnspringClient, PagingRequest}; + +#[tokio::main] +async fn main() -> onspring::Result<()> { + let base_url = std::env::var("API_BASE_URL").expect("API_BASE_URL is required"); + let api_key = std::env::var("SANDBOX_API_KEY").expect("SANDBOX_API_KEY is required"); + let app_id: i32 = std::env::var("TEST_SURVEY_ID") + .expect("TEST_SURVEY_ID is required") + .parse() + .expect("TEST_SURVEY_ID must be an integer"); + + let client = OnspringClient::builder(&api_key) + .base_url(&base_url) + .build(); + + // List reports for an app + let paging = PagingRequest { + page_number: 1, + page_size: 10, + }; + let response = client.list_reports(app_id, Some(paging)).await?; + println!("Total reports: {:?}", response.total_records); + + if let Some(items) = &response.items { + for report in items { + println!( + " Report: {} (id: {}, app_id: {})", + report.name.as_deref().unwrap_or("N/A"), + report.id, + report.app_id, + ); + } + + // Get report data for the first report + if let Some(first) = items.first() { + let data = client.get_report(first.id, None, None).await?; + println!( + "\nReport '{}' data:", + first.name.as_deref().unwrap_or("N/A") + ); + println!(" Columns: {:?}", data.columns); + + if let Some(rows) = &data.rows { + println!(" Rows: {}", rows.len()); + for (i, row) in rows.iter().take(3).enumerate() { + println!( + " Row {}: record_id={:?}, cells={:?}", + i, row.record_id, row.cells + ); + } + } + } + } + + Ok(()) +} diff --git a/tests/integration_apps.rs b/tests/integration_apps.rs new file mode 100644 index 0000000..0700efa --- /dev/null +++ b/tests/integration_apps.rs @@ -0,0 +1,115 @@ +mod integration_common; + +use integration_common::*; +use onspring::PagingRequest; + +#[tokio::test] +#[ignore] +async fn list_apps_should_return_apps() { + let client = build_client(); + let response = client.list_apps(None).await.unwrap(); + + assert!(response.total_records.unwrap() > 0); + let items = response.items.unwrap(); + assert!(!items.is_empty()); + + for app in &items { + assert!(app.id > 0); + assert!(app.name.is_some()); + assert!(app.href.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn list_apps_should_respect_paging() { + let client = build_client(); + let paging = PagingRequest { + page_number: 1, + page_size: 1, + }; + let response = client.list_apps(Some(paging)).await.unwrap(); + + assert_eq!(response.page_number.unwrap(), 1); + assert_eq!(response.page_size.unwrap(), 1); + assert_eq!(response.items.unwrap().len(), 1); +} + +#[tokio::test] +#[ignore] +async fn list_apps_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.list_apps(None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_app_should_return_app() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID"); + let app = client.get_app(app_id).await.unwrap(); + + assert_eq!(app.id, app_id); + assert!(app.name.is_some()); + assert!(app.href.is_some()); +} + +#[tokio::test] +#[ignore] +async fn get_app_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.get_app(1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_app_should_fail_when_not_found() { + let client = build_client(); + let result = client.get_app(0).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_app_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let result = client.get_app(app_id).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_apps_should_return_apps() { + let client = build_client(); + let app_ids = required_env_csv_i32("TEST_APP_IDS"); + let response = client.batch_get_apps(&app_ids).await.unwrap(); + + let items = response.items.unwrap(); + assert_eq!(items.len(), app_ids.len()); + + for app in &items { + assert!(app.id > 0); + assert!(app.name.is_some()); + assert!(app.href.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn batch_get_apps_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.batch_get_apps(&[1]).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_apps_should_fail_when_no_access() { + let client = build_client(); + let app_ids = required_env_csv_i32("TEST_APP_IDS_NO_ACCESS"); + let result = client.batch_get_apps(&app_ids).await; + assert!(result.is_err()); +} diff --git a/tests/integration_common/mod.rs b/tests/integration_common/mod.rs new file mode 100644 index 0000000..292d84e --- /dev/null +++ b/tests/integration_common/mod.rs @@ -0,0 +1,54 @@ +use onspring::OnspringClient; +use std::collections::HashMap; + +pub fn required_env(name: &str) -> String { + std::env::var(name).unwrap_or_else(|_| panic!("{} is not defined", name)) +} + +pub fn required_env_i32(name: &str) -> i32 { + required_env(name) + .parse() + .unwrap_or_else(|_| panic!("{} is not a valid integer", name)) +} + +pub fn required_env_csv_i32(name: &str) -> Vec { + required_env(name) + .split(',') + .map(|s| { + s.trim() + .parse() + .unwrap_or_else(|_| panic!("{} contains non-integer values", name)) + }) + .collect() +} + +pub fn build_client() -> OnspringClient { + let base_url = required_env("API_BASE_URL"); + let api_key = required_env("SANDBOX_API_KEY"); + OnspringClient::builder(&api_key) + .base_url(&base_url) + .build() +} + +pub fn build_client_with_key(api_key: &str) -> OnspringClient { + let base_url = required_env("API_BASE_URL"); + OnspringClient::builder(api_key).base_url(&base_url).build() +} + +pub async fn add_record() -> i32 { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env("TEST_TEXT_FIELD"); + + let mut fields = HashMap::new(); + fields.insert(field_id, serde_json::json!("test")); + + let request = onspring::SaveRecordRequest { + app_id, + record_id: None, + fields, + }; + + let response = client.save_record(request).await.unwrap(); + response.id +} diff --git a/tests/integration_fields.rs b/tests/integration_fields.rs new file mode 100644 index 0000000..b409d20 --- /dev/null +++ b/tests/integration_fields.rs @@ -0,0 +1,126 @@ +mod integration_common; + +use integration_common::*; +use onspring::PagingRequest; + +#[tokio::test] +#[ignore] +async fn get_field_should_return_field() { + let client = build_client(); + let field_id = required_env_i32("TEST_FIELD_ID"); + let field = client.get_field(field_id).await.unwrap(); + + assert_eq!(field.id, field_id); + assert!(field.name.is_some()); + assert!(field.app_id > 0); +} + +#[tokio::test] +#[ignore] +async fn get_field_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.get_field(1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_field_should_fail_when_no_access() { + let client = build_client(); + let field_id = required_env_i32("TEST_FIELD_ID_NO_ACCESS"); + let result = client.get_field(field_id).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_field_should_fail_when_not_found() { + let client = build_client(); + let result = client.get_field(0).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_fields_should_return_fields() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let response = client.list_fields(app_id, None).await.unwrap(); + + assert!(response.total_records.unwrap() > 0); + let items = response.items.unwrap(); + assert!(!items.is_empty()); + + for field in &items { + assert!(field.id > 0); + assert!(field.name.is_some()); + assert!(field.app_id > 0); + } +} + +#[tokio::test] +#[ignore] +async fn list_fields_should_respect_paging() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let paging = PagingRequest { + page_number: 1, + page_size: 1, + }; + let response = client.list_fields(app_id, Some(paging)).await.unwrap(); + + assert_eq!(response.page_number.unwrap(), 1); + assert_eq!(response.page_size.unwrap(), 1); + assert_eq!(response.items.unwrap().len(), 1); +} + +#[tokio::test] +#[ignore] +async fn list_fields_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.list_fields(1, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_fields_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let result = client.list_fields(app_id, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_fields_should_return_fields() { + let client = build_client(); + let field_ids = required_env_csv_i32("TEST_FIELD_IDS"); + let response = client.batch_get_fields(&field_ids).await.unwrap(); + + let items = response.items.unwrap(); + assert_eq!(items.len(), field_ids.len()); + + for field in &items { + assert!(field.id > 0); + assert!(field.name.is_some()); + assert!(field.app_id > 0); + } +} + +#[tokio::test] +#[ignore] +async fn batch_get_fields_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.batch_get_fields(&[1, 2, 3]).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_fields_should_fail_when_no_access() { + let client = build_client(); + let field_ids = required_env_csv_i32("TEST_FIELD_IDS_NO_ACCESS"); + let result = client.batch_get_fields(&field_ids).await; + assert!(result.is_err()); +} diff --git a/tests/integration_files.rs b/tests/integration_files.rs new file mode 100644 index 0000000..e8115e0 --- /dev/null +++ b/tests/integration_files.rs @@ -0,0 +1,340 @@ +mod integration_common; + +use integration_common::*; +use onspring::SaveFileRequest; + +#[tokio::test] +#[ignore] +async fn get_file_info_for_attachment_should_return_info() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let file_id = required_env_i32("TEST_ATTACHMENT"); + + let info = client + .get_file_info(record_id, field_id, file_id) + .await + .unwrap(); + + assert!(info.name.is_some()); + assert!(info.content_type.is_some()); + assert!(info.created_date.is_some()); + assert!(info.modified_date.is_some()); + assert!(info.file_href.is_some()); + assert!(info.owner.is_some()); + assert!(info.file_type.is_some()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_for_image_should_return_info() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_IMAGE_FIELD"); + let file_id = required_env_i32("TEST_IMAGE"); + + let info = client + .get_file_info(record_id, field_id, file_id) + .await + .unwrap(); + + assert!(info.name.is_some()); + assert!(info.content_type.is_some()); + assert!(info.created_date.is_some()); + assert!(info.modified_date.is_some()); + assert!(info.file_href.is_some()); + assert!(info.owner.is_some()); + assert!(info.file_type.is_some()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_for_non_file_field() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_TEXT_FIELD"); + let file_id = required_env_i32("TEST_ATTACHMENT"); + + let result = client.get_file_info(record_id, field_id, file_id).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let file_id = required_env_i32("TEST_ATTACHMENT"); + + let result = client.get_file_info(record_id, field_id, file_id).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_when_no_field_access() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD_NO_ACCESS_FIELD"); + let result = client.get_file_info(1, field_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_when_no_app_access() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD_NO_ACCESS_APP"); + let result = client.get_file_info(1, field_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_when_field_not_found() { + let client = build_client(); + let result = client.get_file_info(1, 0, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_info_should_fail_when_record_not_found() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let result = client.get_file_info(0, field_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_file_for_attachment_should_return_file() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let file_id = required_env_i32("TEST_ATTACHMENT"); + + let response = client.get_file(record_id, field_id, file_id).await.unwrap(); + + assert!(response.content_type.is_some()); + assert!(response.file_name.is_some()); + assert!(!response.data.is_empty()); +} + +#[tokio::test] +#[ignore] +async fn get_file_for_image_should_return_file() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_IMAGE_FIELD"); + let file_id = required_env_i32("TEST_IMAGE"); + + let response = client.get_file(record_id, field_id, file_id).await.unwrap(); + + assert!(response.content_type.is_some()); + assert!(response.file_name.is_some()); + assert!(!response.data.is_empty()); +} + +#[tokio::test] +#[ignore] +async fn get_file_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let file_id = required_env_i32("TEST_ATTACHMENT"); + + let result = client.get_file(record_id, field_id, file_id).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn upload_file_to_attachment_field_should_succeed() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + + let file_data = std::fs::read("tests/test_data/test-attachment.txt").unwrap(); + + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("integration test".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test-attachment.txt".to_string(), + file_data, + content_type: "text/plain".to_string(), + }; + + let response = client.upload_file(request).await.unwrap(); + assert!(response.id > 0); + + // Cleanup + let _ = client.delete_file(record_id, field_id, response.id).await; +} + +#[tokio::test] +#[ignore] +async fn upload_file_to_image_field_should_succeed() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_IMAGE_FIELD"); + + let file_data = std::fs::read("tests/test_data/test-image.jpeg").unwrap(); + + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("integration test".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test-image.jpeg".to_string(), + file_data, + content_type: "image/jpeg".to_string(), + }; + + let response = client.upload_file(request).await.unwrap(); + assert!(response.id > 0); + + // Cleanup + let _ = client.delete_file(record_id, field_id, response.id).await; +} + +#[tokio::test] +#[ignore] +async fn upload_file_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + + let request = SaveFileRequest { + record_id: 1, + field_id: 1, + notes: Some("test".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test.txt".to_string(), + file_data: b"test".to_vec(), + content_type: "text/plain".to_string(), + }; + + let result = client.upload_file(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn upload_file_should_fail_for_non_file_field() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_TEXT_FIELD"); + + let file_data = std::fs::read("tests/test_data/test-attachment.txt").unwrap(); + + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("test".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test-attachment.txt".to_string(), + file_data, + content_type: "text/plain".to_string(), + }; + + let result = client.upload_file(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_file_from_attachment_should_succeed() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + + // Upload first + let file_data = std::fs::read("tests/test_data/test-attachment.txt").unwrap(); + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("to be deleted".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test-attachment.txt".to_string(), + file_data, + content_type: "text/plain".to_string(), + }; + let uploaded = client.upload_file(request).await.unwrap(); + + // Delete + client + .delete_file(record_id, field_id, uploaded.id) + .await + .unwrap(); +} + +#[tokio::test] +#[ignore] +async fn delete_file_from_image_should_succeed() { + let client = build_client(); + let record_id = required_env_i32("TEST_RECORD"); + let field_id = required_env_i32("TEST_IMAGE_FIELD"); + + // Upload first + let file_data = std::fs::read("tests/test_data/test-image.jpeg").unwrap(); + let request = SaveFileRequest { + record_id, + field_id, + notes: Some("to be deleted".to_string()), + modified_date: Some(chrono::Utc::now()), + file_name: "test-image.jpeg".to_string(), + file_data, + content_type: "image/jpeg".to_string(), + }; + let uploaded = client.upload_file(request).await.unwrap(); + + // Delete + client + .delete_file(record_id, field_id, uploaded.id) + .await + .unwrap(); +} + +#[tokio::test] +#[ignore] +async fn delete_file_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.delete_file(1, 1, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_file_should_fail_when_no_field_access() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD_NO_ACCESS_FIELD"); + let result = client.delete_file(1, field_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_file_should_fail_when_no_app_access() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD_NO_ACCESS_APP"); + let result = client.delete_file(1, field_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_file_should_fail_when_field_not_found() { + let client = build_client(); + let result = client.delete_file(1, 0, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_file_should_fail_when_record_not_found() { + let client = build_client(); + let field_id = required_env_i32("TEST_ATTACHMENT_FIELD"); + let result = client.delete_file(0, field_id, 1).await; + assert!(result.is_err()); +} diff --git a/tests/integration_lists.rs b/tests/integration_lists.rs new file mode 100644 index 0000000..8d39ee8 --- /dev/null +++ b/tests/integration_lists.rs @@ -0,0 +1,166 @@ +mod integration_common; + +use integration_common::*; +use onspring::SaveListItemRequest; + +#[tokio::test] +#[ignore] +async fn save_list_item_should_add_item() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID"); + + let name = format!("added_list_value_{}", chrono::Utc::now().timestamp_millis()); + let request = SaveListItemRequest { + id: None, + name, + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + + let response = client.save_list_item(list_id, request).await.unwrap(); + let item_id = response.id; + + // Cleanup + let _ = client.delete_list_item(list_id, item_id).await; +} + +#[tokio::test] +#[ignore] +async fn save_list_item_should_update_item() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID"); + + // Create + let name = format!("added_list_value_{}", chrono::Utc::now().timestamp_millis()); + let request = SaveListItemRequest { + id: None, + name, + numeric_value: None, + color: None, + }; + let created = client.save_list_item(list_id, request).await.unwrap(); + + // Update + let name = format!( + "updated_list_value_{}", + chrono::Utc::now().timestamp_millis() + ); + let update_request = SaveListItemRequest { + id: Some(created.id), + name, + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + let _response = client + .save_list_item(list_id, update_request) + .await + .unwrap(); + + // Cleanup + let _ = client.delete_list_item(list_id, created.id).await; +} + +#[tokio::test] +#[ignore] +async fn save_list_item_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let request = SaveListItemRequest { + id: None, + name: "test".to_string(), + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + let result = client.save_list_item(1, request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn save_list_item_should_fail_when_list_not_found() { + let client = build_client(); + let request = SaveListItemRequest { + id: None, + name: "test".to_string(), + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + let result = client.save_list_item(0, request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn save_list_item_should_fail_when_item_not_found() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID"); + let fake_uuid = uuid::Uuid::parse_str("3fa85f64-5717-4562-b3fc-2c963f66afa6").unwrap(); + + let request = SaveListItemRequest { + id: Some(fake_uuid), + name: "test".to_string(), + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + let result = client.save_list_item(list_id, request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_list_item_should_succeed() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID"); + + // Create first + let name = format!("to_delete_{}", chrono::Utc::now().timestamp_millis()); + let request = SaveListItemRequest { + id: None, + name, + numeric_value: Some(1.0), + color: Some("#000000".to_string()), + }; + let created = client.save_list_item(list_id, request).await.unwrap(); + + // Delete + client.delete_list_item(list_id, created.id).await.unwrap(); +} + +#[tokio::test] +#[ignore] +async fn delete_list_item_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let fake_uuid = uuid::Uuid::parse_str("3fa85f64-5717-4562-b3fc-2c963f66afa6").unwrap(); + let result = client.delete_list_item(1, fake_uuid).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_list_item_should_fail_when_no_access() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID_NO_ACCESS"); + let item_id = required_env("TEST_LIST_ITEM_ID_NO_ACCESS"); + let uuid = uuid::Uuid::parse_str(&item_id).unwrap(); + + let result = client.delete_list_item(list_id, uuid).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_list_item_should_fail_when_list_not_found() { + let client = build_client(); + let fake_uuid = uuid::Uuid::parse_str("3fa85f64-5717-4562-b3fc-2c963f66afa6").unwrap(); + let result = client.delete_list_item(0, fake_uuid).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_list_item_should_fail_when_item_not_found() { + let client = build_client(); + let list_id = required_env_i32("TEST_LIST_ID"); + let fake_uuid = uuid::Uuid::parse_str("3fa85f64-5717-4562-b3fc-2c963f66afa6").unwrap(); + let result = client.delete_list_item(list_id, fake_uuid).await; + assert!(result.is_err()); +} diff --git a/tests/integration_ping.rs b/tests/integration_ping.rs new file mode 100644 index 0000000..846bb69 --- /dev/null +++ b/tests/integration_ping.rs @@ -0,0 +1,18 @@ +mod integration_common; + +use integration_common::{build_client, build_client_with_key}; + +#[tokio::test] +#[ignore] +async fn ping_should_succeed() { + let client = build_client(); + client.ping().await.unwrap(); +} + +#[tokio::test] +#[ignore] +async fn ping_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.ping().await; + assert!(result.is_err()); +} diff --git a/tests/integration_records.rs b/tests/integration_records.rs new file mode 100644 index 0000000..db5edd7 --- /dev/null +++ b/tests/integration_records.rs @@ -0,0 +1,441 @@ +mod integration_common; + +use integration_common::*; +use onspring::{ + BatchDeleteRecordsRequest, BatchGetRecordsRequest, DataFormat, PagingRequest, + QueryRecordsRequest, SaveRecordRequest, +}; +use std::collections::HashMap; + +#[tokio::test] +#[ignore] +async fn list_records_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let response = client.list_records(app_id, None, None, None).await.unwrap(); + + assert!(response.total_records.unwrap() > 0); + let items = response.items.unwrap(); + assert!(!items.is_empty()); + + for record in &items { + assert_eq!(record.app_id, app_id); + assert!(record.record_id > 0); + assert!(record.field_data.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn list_records_with_params_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env_i32("TEST_TEXT_FIELD"); + let paging = PagingRequest { + page_number: 1, + page_size: 1, + }; + + let response = client + .list_records( + app_id, + Some(paging), + Some(&[field_id]), + Some(DataFormat::Formatted), + ) + .await + .unwrap(); + + assert_eq!(response.page_number.unwrap(), 1); + assert_eq!(response.page_size.unwrap(), 1); + + for record in response.items.unwrap_or_default() { + assert_eq!(record.app_id, app_id); + assert!(record.field_data.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn list_records_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.list_records(1, None, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_records_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let result = client.list_records(app_id, None, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_record_should_return_record() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let record_id = required_env_i32("TEST_SURVEY_RECORD_ID"); + + let record = client + .get_record(app_id, record_id, None, None) + .await + .unwrap(); + + assert_eq!(record.app_id, app_id); + assert_eq!(record.record_id, record_id); + assert!(record.field_data.is_some()); +} + +#[tokio::test] +#[ignore] +async fn get_record_with_params_should_return_record() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let record_id = required_env_i32("TEST_SURVEY_RECORD_ID"); + let field_id = required_env_i32("TEST_TEXT_FIELD"); + + let record = client + .get_record( + app_id, + record_id, + Some(&[field_id]), + Some(DataFormat::Formatted), + ) + .await + .unwrap(); + + assert_eq!(record.app_id, app_id); + assert_eq!(record.record_id, record_id); + assert!(record.field_data.is_some()); +} + +#[tokio::test] +#[ignore] +async fn get_record_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.get_record(1, 1, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_record_should_fail_when_not_found() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let result = client.get_record(app_id, 0, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn save_record_should_create_record() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env("TEST_TEXT_FIELD"); + + let mut fields = HashMap::new(); + fields.insert(field_id, serde_json::json!("integration test create")); + + let request = SaveRecordRequest { + app_id, + record_id: None, + fields, + }; + + let response = client.save_record(request).await.unwrap(); + assert!(response.id > 0); + + // Cleanup + let _ = client.delete_record(app_id, response.id).await; +} + +#[tokio::test] +#[ignore] +async fn save_record_should_update_record() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env("TEST_TEXT_FIELD"); + + // Create a record first + let mut fields = HashMap::new(); + fields.insert(field_id.clone(), serde_json::json!("integration test")); + let create_request = SaveRecordRequest { + app_id, + record_id: None, + fields, + }; + let created = client.save_record(create_request).await.unwrap(); + + // Update it + let mut fields = HashMap::new(); + fields.insert(field_id, serde_json::json!("updated")); + let update_request = SaveRecordRequest { + app_id, + record_id: Some(created.id), + fields, + }; + let response = client.save_record(update_request).await.unwrap(); + assert_eq!(response.id, created.id); + + // Cleanup + let _ = client.delete_record(app_id, created.id).await; +} + +#[tokio::test] +#[ignore] +async fn save_record_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let request = SaveRecordRequest { + app_id: 0, + record_id: None, + fields: HashMap::new(), + }; + let result = client.save_record(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn save_record_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let request = SaveRecordRequest { + app_id, + record_id: None, + fields: HashMap::new(), + }; + let result = client.save_record(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_record_should_succeed() { + let record_id = add_record().await; + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + + client.delete_record(app_id, record_id).await.unwrap(); +} + +#[tokio::test] +#[ignore] +async fn delete_record_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.delete_record(1, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_record_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let result = client.delete_record(app_id, 1).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn delete_record_should_fail_when_not_found() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID"); + let result = client.delete_record(app_id, 0).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_records_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let record_id = required_env_i32("TEST_SURVEY_RECORD_ID"); + + let request = BatchGetRecordsRequest { + app_id, + record_ids: vec![record_id], + field_ids: None, + data_format: None, + }; + + let response = client.batch_get_records(request).await.unwrap(); + let items = response.items.unwrap(); + assert!(!items.is_empty()); + + for record in &items { + assert_eq!(record.app_id, app_id); + assert!(record.field_data.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn batch_get_records_with_params_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let record_id = required_env_i32("TEST_SURVEY_RECORD_ID"); + let field_id = required_env_i32("TEST_TEXT_FIELD"); + + let request = BatchGetRecordsRequest { + app_id, + record_ids: vec![record_id], + field_ids: Some(vec![field_id]), + data_format: Some(DataFormat::Formatted), + }; + + let response = client.batch_get_records(request).await.unwrap(); + let items = response.items.unwrap(); + assert!(!items.is_empty()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_records_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let request = BatchGetRecordsRequest { + app_id: 1, + record_ids: vec![1], + field_ids: None, + data_format: None, + }; + let result = client.batch_get_records(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_get_records_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let request = BatchGetRecordsRequest { + app_id, + record_ids: vec![1], + field_ids: None, + data_format: None, + }; + let result = client.batch_get_records(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn query_records_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env_i32("TEST_SURVEY_AUTO_NUMBER_FIELD"); + + let filter = format!("{} gt 0", field_id); + let request = QueryRecordsRequest { + app_id, + filter, + field_ids: None, + data_format: None, + }; + + let response = client.query_records(request, None).await.unwrap(); + assert!(response.total_records.unwrap() > 0); + + for record in response.items.unwrap_or_default() { + assert_eq!(record.app_id, app_id); + } +} + +#[tokio::test] +#[ignore] +async fn query_records_with_params_should_return_records() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let field_id = required_env_i32("TEST_SURVEY_AUTO_NUMBER_FIELD"); + + let filter = format!("{} gt 0", field_id); + let paging = PagingRequest { + page_number: 1, + page_size: 1, + }; + let request = QueryRecordsRequest { + app_id, + filter, + field_ids: Some(vec![field_id]), + data_format: Some(DataFormat::Formatted), + }; + + let response = client.query_records(request, Some(paging)).await.unwrap(); + assert_eq!(response.page_size.unwrap(), 1); +} + +#[tokio::test] +#[ignore] +async fn query_records_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let request = QueryRecordsRequest { + app_id: 1, + filter: String::new(), + field_ids: None, + data_format: None, + }; + let result = client.query_records(request, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn query_records_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let field_id = required_env_i32("TEST_SURVEY_AUTO_NUMBER_FIELD"); + + let filter = format!("{} gt 0", field_id); + let request = QueryRecordsRequest { + app_id, + filter, + field_ids: None, + data_format: None, + }; + let result = client.query_records(request, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_delete_records_should_succeed() { + let record_id1 = add_record().await; + let record_id2 = add_record().await; + + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + + let request = BatchDeleteRecordsRequest { + app_id, + record_ids: vec![record_id1, record_id2], + }; + + client.batch_delete_records(request).await.unwrap(); +} + +#[tokio::test] +#[ignore] +async fn batch_delete_records_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let request = BatchDeleteRecordsRequest { + app_id: 1, + record_ids: vec![1], + }; + let result = client.batch_delete_records(request).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn batch_delete_records_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let request = BatchDeleteRecordsRequest { + app_id, + record_ids: vec![1], + }; + let result = client.batch_delete_records(request).await; + assert!(result.is_err()); +} diff --git a/tests/integration_reports.rs b/tests/integration_reports.rs new file mode 100644 index 0000000..f4a87a8 --- /dev/null +++ b/tests/integration_reports.rs @@ -0,0 +1,124 @@ +mod integration_common; + +use integration_common::*; +use onspring::{DataFormat, PagingRequest, ReportDataType}; + +#[tokio::test] +#[ignore] +async fn get_report_should_return_report() { + let client = build_client(); + let report_id = required_env_i32("TEST_REPORT"); + + let report = client.get_report(report_id, None, None).await.unwrap(); + + let columns = report.columns.unwrap(); + assert!(!columns.is_empty()); + + let rows = report.rows.unwrap(); + assert!(!rows.is_empty()); + + for row in &rows { + assert!(row.record_id.unwrap() > 0); + assert!(!row.cells.as_ref().unwrap().is_empty()); + } +} + +#[tokio::test] +#[ignore] +async fn get_report_with_chart_data_should_return_data() { + let client = build_client(); + let report_id = required_env_i32("TEST_REPORT_WITH_CHART_DATA"); + + let report = client + .get_report( + report_id, + Some(DataFormat::Raw), + Some(ReportDataType::ChartData), + ) + .await + .unwrap(); + + assert!(!report.columns.unwrap().is_empty()); + assert!(!report.rows.unwrap().is_empty()); +} + +#[tokio::test] +#[ignore] +async fn get_report_should_fail_for_chart_data_on_non_chart_report() { + let client = build_client(); + let report_id = required_env_i32("TEST_REPORT"); + + let result = client + .get_report( + report_id, + Some(DataFormat::Raw), + Some(ReportDataType::ChartData), + ) + .await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_report_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.get_report(1, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn get_report_should_fail_when_not_found() { + let client = build_client(); + let result = client.get_report(0, None, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_reports_should_return_reports() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + + let response = client.list_reports(app_id, None).await.unwrap(); + + assert!(response.total_records.unwrap() > 0); + let items = response.items.unwrap(); + assert!(!items.is_empty()); + + for report in &items { + assert!(report.id > 0); + assert!(report.app_id > 0); + assert!(report.name.is_some()); + } +} + +#[tokio::test] +#[ignore] +async fn list_reports_should_fail_with_invalid_api_key() { + let client = build_client_with_key("invalid"); + let result = client.list_reports(1, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_reports_should_fail_when_no_access() { + let client = build_client(); + let app_id = required_env_i32("TEST_APP_ID_NO_ACCESS"); + let result = client.list_reports(app_id, None).await; + assert!(result.is_err()); +} + +#[tokio::test] +#[ignore] +async fn list_reports_should_fail_with_invalid_page_size() { + let client = build_client(); + let app_id = required_env_i32("TEST_SURVEY_ID"); + let paging = PagingRequest { + page_number: 1, + page_size: 1001, + }; + let result = client.list_reports(app_id, Some(paging)).await; + assert!(result.is_err()); +} diff --git a/tests/test_data/test-attachment.txt b/tests/test_data/test-attachment.txt new file mode 100644 index 0000000..6bcfe34 --- /dev/null +++ b/tests/test_data/test-attachment.txt @@ -0,0 +1 @@ +This is a test attachment file for integration tests. \ No newline at end of file diff --git a/tests/test_data/test-image.jpeg b/tests/test_data/test-image.jpeg new file mode 100644 index 0000000..b493033 Binary files /dev/null and b/tests/test_data/test-image.jpeg differ