style: format code and reorganize imports across rfsclient codebase

This commit is contained in:
Sameh Abouel-saad
2025-08-28 03:50:07 +03:00
parent e114404ca7
commit b2fc0976bd
11 changed files with 795 additions and 469 deletions

View File

@@ -1,5 +1,5 @@
use sal_rfs_client::RfsClient;
use sal_rfs_client::types::{ClientConfig, Credentials};
use sal_rfs_client::RfsClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -12,10 +12,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
timeout_seconds: 30,
};
let mut client = RfsClient::new(config);
println!("Client created with authentication credentials");
// Authenticate with the server
client.authenticate().await?;
if client.is_authenticated() {
@@ -30,13 +30,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
credentials: None,
timeout_seconds: 30,
};
let client_no_auth = RfsClient::new(config_no_auth);
println!("Client created without authentication credentials");
// Check health endpoint (doesn't require authentication)
let health = client_no_auth.health_check().await?;
println!("Server health: {:?}", health);
Ok(())
}

View File

@@ -1,6 +1,6 @@
use sal_rfs_client::RfsClient;
use sal_rfs_client::types::{ClientConfig, Credentials};
use openapi::models::{VerifyBlock, VerifyBlocksRequest};
use sal_rfs_client::types::{ClientConfig, Credentials};
use sal_rfs_client::RfsClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -13,45 +13,52 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
timeout_seconds: 60,
};
let mut client = RfsClient::new(config);
// Authenticate with the server
client.authenticate().await?;
println!("Authentication successful");
// Create a test file to upload for block testing
let test_file_path = "/tmp/block_test.txt";
let test_content = "This is a test file for RFS client block management";
std::fs::write(test_file_path, test_content)?;
println!("Created test file at {}", test_file_path);
// Upload the file to get blocks
println!("Uploading file to get blocks...");
let file_hash = client.upload_file(test_file_path, None).await?;
println!("File uploaded with hash: {}", file_hash);
// Get blocks by file hash
println!("Getting blocks for file hash: {}", file_hash);
let blocks = client.get_blocks_by_hash(&file_hash).await?;
println!("Found {} blocks for the file", blocks.blocks.len());
// Print block information
for (i, block_data) in blocks.blocks.iter().enumerate() {
println!("Block {}: Hash={}, Index={}", i, block_data.hash, block_data.index);
println!(
"Block {}: Hash={}, Index={}",
i, block_data.hash, block_data.index
);
}
// Verify blocks with complete information
println!("Verifying blocks...");
// Create a list of VerifyBlock objects with complete information
let verify_blocks = blocks.blocks.iter().map(|block| {
VerifyBlock {
block_hash: block.hash.clone(),
block_index: block.index,
file_hash: file_hash.clone(), // Using the actual file hash
}
}).collect::<Vec<_>>();
let verify_blocks = blocks
.blocks
.iter()
.map(|block| {
VerifyBlock {
block_hash: block.hash.clone(),
block_index: block.index,
file_hash: file_hash.clone(), // Using the actual file hash
}
})
.collect::<Vec<_>>();
// Create the request with the complete block information
for block in verify_blocks.iter() {
@@ -59,27 +66,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Block index: {}", block.block_index);
println!("File hash: {}", block.file_hash);
}
let request = VerifyBlocksRequest { blocks: verify_blocks };
let request = VerifyBlocksRequest {
blocks: verify_blocks,
};
// Send the verification request
let verify_result = client.verify_blocks(request).await?;
println!("Verification result: {} missing blocks", verify_result.missing.len());
println!(
"Verification result: {} missing blocks",
verify_result.missing.len()
);
for block in verify_result.missing.iter() {
println!("Missing block: {}", block);
}
// List blocks (list_blocks_handler)
println!("\n1. Listing all blocks with pagination...");
let blocks_list = client.list_blocks(None).await?;
println!("Server has {} blocks in total", blocks_list.len());
if !blocks_list.is_empty() {
let first_few = blocks_list.iter().take(3)
let first_few = blocks_list
.iter()
.take(3)
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ");
println!("First few blocks: {}", first_few);
}
// Check if a block exists (check_block_handler)
if !blocks.blocks.is_empty() {
let block_to_check = &blocks.blocks[0].hash;
@@ -87,15 +101,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let exists = client.check_block(block_to_check).await?;
println!("Block exists: {}", exists);
}
// Get block downloads statistics (get_block_downloads_handler)
if !blocks.blocks.is_empty() {
let block_to_check = &blocks.blocks[0].hash;
println!("\n3. Getting download statistics for block: {}", block_to_check);
println!(
"\n3. Getting download statistics for block: {}",
block_to_check
);
let downloads = client.get_block_downloads(block_to_check).await?;
println!("Block has been downloaded {} times", downloads.downloads_count);
println!(
"Block has been downloaded {} times",
downloads.downloads_count
);
}
// Get a specific block content (get_block_handler)
if !blocks.blocks.is_empty() {
let block_to_get = &blocks.blocks[0].hash;
@@ -103,26 +123,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let block_content = client.get_block(block_to_get).await?;
println!("Retrieved block with {} bytes", block_content.len());
}
// Get user blocks (get_user_blocks_handler)
println!("\n6. Listing user blocks...");
let user_blocks = client.get_user_blocks(Some(1), Some(10)).await?;
println!("User has {} blocks (showing page 1 with 10 per page)", user_blocks.total);
println!(
"User has {} blocks (showing page 1 with 10 per page)",
user_blocks.total
);
for block in user_blocks.blocks.iter().take(3) {
println!(" - Block: {}, Size: {}", block.hash, block.size);
}
// Upload a block (upload_block_handler)
println!("\n7. Uploading a new test block...");
let test_block_data = b"This is test block data for direct block upload";
let new_file_hash = "test_file_hash_for_block_upload";
let block_index = 0;
let block_hash = client.upload_block(new_file_hash, block_index, test_block_data.to_vec()).await?;
let block_hash = client
.upload_block(new_file_hash, block_index, test_block_data.to_vec())
.await?;
println!("Uploaded block with hash: {}", block_hash);
// Clean up
std::fs::remove_file(test_file_path)?;
println!("Test file cleaned up");
Ok(())
}

View File

@@ -1,5 +1,5 @@
use sal_rfs_client::types::{ClientConfig, Credentials, DownloadOptions, UploadOptions};
use sal_rfs_client::RfsClient;
use sal_rfs_client::types::{ClientConfig, Credentials, UploadOptions, DownloadOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -12,53 +12,55 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
timeout_seconds: 60,
};
let mut client = RfsClient::new(config);
// Authenticate with the server
client.authenticate().await?;
println!("Authentication successful");
// Create a test file to upload
let test_file_path = "/tmp/test_upload.txt";
std::fs::write(test_file_path, "This is a test file for RFS client upload")?;
println!("Created test file at {}", test_file_path);
// Upload the file with options
println!("Uploading file...");
let upload_options = UploadOptions {
chunk_size: Some(1024 * 1024), // 1MB chunks
verify: true,
};
let file_hash = client.upload_file(test_file_path, Some(upload_options)).await?;
let file_hash = client
.upload_file(test_file_path, Some(upload_options))
.await?;
println!("File uploaded with hash: {}", file_hash);
// Download the file
let download_path = "/tmp/test_download.txt";
println!("Downloading file to {}...", download_path);
let download_options = DownloadOptions {
verify: true,
};
client.download_file(&file_hash, download_path, Some(download_options)).await?;
let download_options = DownloadOptions { verify: true };
client
.download_file(&file_hash, download_path, Some(download_options))
.await?;
println!("File downloaded to {}", download_path);
// Verify the downloaded file matches the original
let original_content = std::fs::read_to_string(test_file_path)?;
let downloaded_content = std::fs::read_to_string(download_path)?;
if original_content == downloaded_content {
println!("File contents match! Download successful.");
} else {
println!("ERROR: File contents do not match!");
}
// Clean up test files
std::fs::remove_file(test_file_path)?;
std::fs::remove_file(download_path)?;
println!("Test files cleaned up");
Ok(())
}

View File

@@ -1,5 +1,5 @@
use sal_rfs_client::RfsClient;
use sal_rfs_client::types::{ClientConfig, Credentials, FlistOptions, WaitOptions};
use sal_rfs_client::RfsClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -13,17 +13,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
timeout_seconds: 60,
};
let mut client = RfsClient::new(config);
// Authenticate with the server
client.authenticate().await?;
println!("Authentication successful");
println!("\n1. CREATE FLIST - Creating an FList from a Docker image");
let image_name = "alpine:latest";
println!("Creating FList for image: {}", image_name);
// Use FlistOptions to specify additional parameters
let options = FlistOptions {
auth: None,
@@ -34,13 +34,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
identity_token: None,
registry_token: None,
};
// Create the FList and handle potential conflict error
let job_id = match client.create_flist(&image_name, Some(options)).await {
Ok(id) => {
println!("FList creation started with job ID: {}", id);
Some(id)
},
}
Err(e) => {
if e.to_string().contains("Conflict") {
println!("FList already exists");
@@ -50,51 +50,55 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
};
// 2. Check FList state if we have a job ID
if let Some(job_id) = &job_id {
println!("\n2. GET FLIST STATE - Checking FList creation state");
let state = client.get_flist_state(job_id).await?;
println!("Current FList state: {:?}", state.flist_state);
// 3. Wait for FList creation with progress reporting
println!("\n3. WAIT FOR FLIST CREATION - Waiting for FList to be created with progress reporting");
let wait_options = WaitOptions {
timeout_seconds: 60, // Shorter timeout for the example
timeout_seconds: 60, // Shorter timeout for the example
poll_interval_ms: 1000,
progress_callback: Some(Box::new(|state| {
println!("Progress: FList state is now {:?}", state);
// No return value needed (returns unit type)
})),
};
// Wait for the FList to be created (with a timeout)
match client.wait_for_flist_creation(job_id, Some(wait_options)).await {
match client
.wait_for_flist_creation(job_id, Some(wait_options))
.await
{
Ok(final_state) => {
println!("FList creation completed with state: {:?}", final_state);
},
}
Err(e) => {
println!("Error waiting for FList creation: {}", e);
// Continue with the example even if waiting fails
}
};
}
// 4. List all available FLists
println!("\n4. LIST FLISTS - Listing all available FLists");
// Variable to store the FList path for preview and download
let mut flist_path_for_preview: Option<String> = None;
match client.list_flists().await {
Ok(flists) => {
println!("Found {} FList categories", flists.len());
for (category, files) in &flists {
println!("Category: {}", category);
for file in files.iter().take(2) { // Show only first 2 files per category
for file in files.iter().take(2) {
// Show only first 2 files per category
println!(" - {} (size: {} bytes)", file.name, file.size);
// Save the first FList path for preview
if flist_path_for_preview.is_none() {
let path = format!("{}/{}/{}", parent_dir, category, file.name);
@@ -105,7 +109,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" - ... and {} more files", files.len() - 2);
}
}
// 5. Preview an FList if we found one
if let Some(ref flist_path) = flist_path_for_preview {
println!("\n5. PREVIEW FLIST - Previewing FList: {}", flist_path);
@@ -114,57 +118,59 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("FList preview for {}:", flist_path);
println!(" - Checksum: {}", preview.checksum);
println!(" - Metadata: {}", preview.metadata);
// Display content (list of strings)
if !preview.content.is_empty() {
println!(" - Content entries:");
for (i, entry) in preview.content.iter().enumerate().take(5) {
println!(" {}. {}", i+1, entry);
println!(" {}. {}", i + 1, entry);
}
if preview.content.len() > 5 {
println!(" ... and {} more entries", preview.content.len() - 5);
}
}
},
}
Err(e) => println!("Error previewing FList: {}", e),
}
} else {
println!("No FLists available for preview");
}
},
}
Err(e) => println!("Error listing FLists: {}", e),
}
// 6. DOWNLOAD FLIST - Downloading an FList to a local file
if let Some(ref flist_path) = flist_path_for_preview {
println!("\n6. DOWNLOAD FLIST - Downloading FList: {}", flist_path);
// Create a temporary output path for the downloaded FList
let output_path = "/tmp/downloaded_flist.fl";
match client.download_flist(flist_path, output_path).await {
Ok(_) => {
println!("FList successfully downloaded to {}", output_path);
// Get file size
match std::fs::metadata(output_path) {
Ok(metadata) => println!("Downloaded file size: {} bytes", metadata.len()),
Err(e) => println!("Error getting file metadata: {}", e),
}
},
}
Err(e) => println!("Error downloading FList: {}", e),
}
} else {
println!("\n6. DOWNLOAD FLIST - No FList available for download");
}
println!("\nAll FList operations demonstrated:");
println!("1. create_flist - Create a new FList from a Docker image");
println!("2. get_flist_state - Check the state of an FList creation job");
println!("3. wait_for_flist_creation - Wait for an FList to be created with progress reporting");
println!(
"3. wait_for_flist_creation - Wait for an FList to be created with progress reporting"
);
println!("4. list_flists - List all available FLists");
println!("5. preview_flist - Preview the content of an FList");
println!("6. download_flist - Download an FList to a local file");
Ok(())
}

View File

@@ -1,6 +1,6 @@
use sal_rfs_client::RfsClient;
use sal_rfs_client::types::{ClientConfig, Credentials, WaitOptions};
use openapi::models::FlistState;
use sal_rfs_client::types::{ClientConfig, Credentials, WaitOptions};
use sal_rfs_client::RfsClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -13,49 +13,52 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
timeout_seconds: 60,
};
let mut client = RfsClient::new(config);
// Authenticate with the server
client.authenticate().await?;
println!("Authentication successful");
// Create an FList from a Docker image
let image_name = "redis:latest";
println!("Creating FList for image: {}", image_name);
let job_id = client.create_flist(&image_name, None).await?;
println!("FList creation started with job ID: {}", job_id);
// Set up options for waiting with progress reporting
let options = WaitOptions {
timeout_seconds: 600, // 10 minutes timeout
timeout_seconds: 600, // 10 minutes timeout
poll_interval_ms: 2000, // Check every 2 seconds
progress_callback: Some(Box::new(|state| {
match state {
FlistState::FlistStateInProgress(info) => {
println!("Progress: {:.1}% - {}", info.in_progress.progress, info.in_progress.msg);
},
FlistState::FlistStateStarted(_) => {
println!("FList creation started...");
},
FlistState::FlistStateAccepted(_) => {
println!("FList creation request accepted...");
},
_ => println!("State: {:?}", state),
progress_callback: Some(Box::new(|state| match state {
FlistState::FlistStateInProgress(info) => {
println!(
"Progress: {:.1}% - {}",
info.in_progress.progress, info.in_progress.msg
);
}
FlistState::FlistStateStarted(_) => {
println!("FList creation started...");
}
FlistState::FlistStateAccepted(_) => {
println!("FList creation request accepted...");
}
_ => println!("State: {:?}", state),
})),
};
// Wait for the FList to be created
println!("Waiting for FList creation to complete...");
// Use ? operator to propagate errors properly
let state = client.wait_for_flist_creation(&job_id, Some(options)).await
let state = client
.wait_for_flist_creation(&job_id, Some(options))
.await
.map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
println!("FList created successfully!");
println!("Final state: {:?}", state);
Ok(())
}

View File

@@ -1,24 +1,23 @@
use bytes::Bytes;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::collections::HashMap;
use bytes::Bytes;
use openapi::{
apis::{
authentication_api, block_management_api, flist_management_api,
file_management_api, system_api, website_serving_api,
configuration::Configuration,
authentication_api, block_management_api, configuration::Configuration,
file_management_api, flist_management_api, system_api, website_serving_api,
Error as OpenApiError,
},
models::{
SignInBody, ListBlocksParams,
VerifyBlocksRequest, VerifyBlocksResponse, FlistBody, UserBlocksResponse, BlockDownloadsResponse,
BlocksResponse, PreviewResponse, FileInfo, FlistState, FlistStateResponse,
BlockDownloadsResponse, BlocksResponse, FileInfo, FlistBody, FlistState,
FlistStateResponse, ListBlocksParams, PreviewResponse, SignInBody, UserBlocksResponse,
VerifyBlocksRequest, VerifyBlocksResponse,
},
};
use crate::error::{RfsError, Result, map_openapi_error};
use crate::types::{ClientConfig, UploadOptions, DownloadOptions, FlistOptions, WaitOptions};
use crate::error::{map_openapi_error, Result, RfsError};
use crate::types::{ClientConfig, DownloadOptions, FlistOptions, UploadOptions, WaitOptions};
/// Main client for interacting with the RFS server
#[derive(Clone)]
@@ -33,16 +32,18 @@ impl RfsClient {
pub fn new(client_config: ClientConfig) -> Self {
// Create a custom reqwest client with timeout configuration
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(client_config.timeout_seconds))
.timeout(std::time::Duration::from_secs(
client_config.timeout_seconds,
))
.build()
.unwrap_or_default();
// Create OpenAPI configuration with our custom client
let mut config = Configuration::new();
config.base_path = client_config.base_url.clone();
config.user_agent = Some(format!("rfs-client/0.1.0"));
config.user_agent = Some("rfs-client/0.1.0".to_string());
config.client = client;
Self {
config: Arc::new(config),
client_config,
@@ -70,22 +71,26 @@ impl RfsClient {
if let Some(token) = Some(result.access_token) {
// Create a custom reqwest client with timeout configuration
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(self.client_config.timeout_seconds))
.timeout(std::time::Duration::from_secs(
self.client_config.timeout_seconds,
))
.build()
.unwrap_or_default();
// Create a new configuration with the auth token and timeout
let mut new_config = Configuration::new();
new_config.base_path = self.client_config.base_url.clone();
new_config.user_agent = Some(format!("rfs-client/0.1.0"));
new_config.user_agent = Some("rfs-client/0.1.0".to_string());
new_config.bearer_access_token = Some(token.clone());
new_config.client = client;
self.config = Arc::new(new_config);
self.auth_token = Some(token);
Ok(())
} else {
Err(RfsError::AuthError("No token received from server".to_string()))
Err(RfsError::AuthError(
"No token received from server".to_string(),
))
}
} else {
Err(RfsError::AuthError("No credentials provided".to_string()))
@@ -102,62 +107,79 @@ impl RfsClient {
let result = system_api::health_check_handler(&self.config)
.await
.map_err(map_openapi_error)?;
Ok(result.msg)
}
/// Upload a file to the RFS server
pub async fn upload_file<P: AsRef<Path>>(&self, file_path: P, options: Option<UploadOptions>) -> Result<String> {
pub async fn upload_file<P: AsRef<Path>>(
&self,
file_path: P,
options: Option<UploadOptions>,
) -> Result<String> {
let file_path = file_path.as_ref();
let _options = options.unwrap_or_default();
// Check if file exists
if !file_path.exists() {
return Err(RfsError::FileSystemError(format!("File not found: {}", file_path.display())));
return Err(RfsError::FileSystemError(format!(
"File not found: {}",
file_path.display()
)));
}
// Use the OpenAPI client to upload the file
let result = file_management_api::upload_file_handler(&self.config, file_path.to_path_buf())
.await
.map_err(map_openapi_error)?;
let result =
file_management_api::upload_file_handler(&self.config, file_path.to_path_buf())
.await
.map_err(map_openapi_error)?;
// Extract the file hash from the response
Ok(result.file_hash.clone())
}
/// Download a file from the RFS server
pub async fn download_file<P: AsRef<Path>>(&self, file_id: &str, output_path: P, options: Option<DownloadOptions>) -> Result<()> {
pub async fn download_file<P: AsRef<Path>>(
&self,
file_id: &str,
output_path: P,
options: Option<DownloadOptions>,
) -> Result<()> {
let output_path = output_path.as_ref();
let _options = options.unwrap_or_default();
// Create parent directories if needed
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| RfsError::FileSystemError(format!("Failed to create directory: {}", e)))?;
std::fs::create_dir_all(parent).map_err(|e| {
RfsError::FileSystemError(format!("Failed to create directory: {}", e))
})?;
}
// Create a FileDownloadRequest with the filename from the output path
let file_name = output_path.file_name()
let file_name = output_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("downloaded_file")
.to_string();
let download_request = openapi::models::FileDownloadRequest::new(file_name);
// Download the file
let response = file_management_api::get_file_handler(&self.config, file_id, download_request)
.await
.map_err(map_openapi_error)?;
let response =
file_management_api::get_file_handler(&self.config, file_id, download_request)
.await
.map_err(map_openapi_error)?;
// Read the response body
let bytes = response.bytes()
let bytes = response
.bytes()
.await
.map_err(|e| RfsError::RequestError(e))?;
.map_err(RfsError::RequestError)?;
// Write the file to disk
std::fs::write(output_path, bytes)
.map_err(|e| RfsError::FileSystemError(format!("Failed to write file: {}", e)))?;
Ok(())
}
@@ -168,29 +190,38 @@ impl RfsClient {
let result = block_management_api::list_blocks_handler(&self.config, page, per_page)
.await
.map_err(map_openapi_error)?;
Ok(result.blocks)
}
/// Verify blocks
pub async fn verify_blocks(&self, request: VerifyBlocksRequest) -> Result<VerifyBlocksResponse> {
pub async fn verify_blocks(
&self,
request: VerifyBlocksRequest,
) -> Result<VerifyBlocksResponse> {
let result = block_management_api::verify_blocks_handler(&self.config, request)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
/// Create a new FList from a Docker image
pub async fn create_flist(&self, image_name: &str, options: Option<FlistOptions>) -> Result<String> {
pub async fn create_flist(
&self,
image_name: &str,
options: Option<FlistOptions>,
) -> Result<String> {
// Ensure the client is authenticated
if !self.is_authenticated() {
return Err(RfsError::AuthError("Authentication required for creating FLists".to_string()));
return Err(RfsError::AuthError(
"Authentication required for creating FLists".to_string(),
));
}
// Create FList body with the required fields
let mut flist = FlistBody::new(image_name.to_string());
// Apply options if provided
if let Some(opts) = options {
flist.username = opts.username.map(Some);
@@ -201,12 +232,12 @@ impl RfsClient {
flist.identity_token = opts.identity_token.map(Some);
flist.registry_token = opts.registry_token.map(Some);
}
// Call the API to create the FList
let result = flist_management_api::create_flist_handler(&self.config, flist)
.await
.map_err(map_openapi_error)?;
// Return the job ID
Ok(result.id)
}
@@ -215,66 +246,80 @@ impl RfsClient {
pub async fn get_flist_state(&self, job_id: &str) -> Result<FlistStateResponse> {
// Ensure the client is authenticated
if !self.is_authenticated() {
return Err(RfsError::AuthError("Authentication required for accessing FList state".to_string()));
return Err(RfsError::AuthError(
"Authentication required for accessing FList state".to_string(),
));
}
// Call the API to get the FList state
let result = flist_management_api::get_flist_state_handler(&self.config, job_id)
.await
.await
.map_err(map_openapi_error)?;
Ok(result)
}
/// Wait for an FList to be created
///
///
/// This method polls the FList state until it reaches a terminal state (Created or Failed)
/// or until the timeout is reached.
pub async fn wait_for_flist_creation(&self, job_id: &str, options: Option<WaitOptions>) -> Result<FlistStateResponse> {
pub async fn wait_for_flist_creation(
&self,
job_id: &str,
options: Option<WaitOptions>,
) -> Result<FlistStateResponse> {
let options = options.unwrap_or_default();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(options.timeout_seconds);
let deadline =
std::time::Instant::now() + std::time::Duration::from_secs(options.timeout_seconds);
loop {
// Check if we've exceeded the timeout
if std::time::Instant::now() > deadline {
return Err(RfsError::TimeoutError(format!(
"Timed out waiting for FList creation after {} seconds",
"Timed out waiting for FList creation after {} seconds",
options.timeout_seconds
)));
}
// Get the current state
let state_result = self.get_flist_state(job_id).await;
match state_result {
Ok(state) => {
// Call progress callback if provided
if let Some(ref callback) = options.progress_callback {
callback(state.flist_state.as_ref());
}
// Check if we've reached a terminal state
match state.flist_state.as_ref() {
FlistState::FlistStateCreated(_) => {
// Success! FList was created
return Ok(state);
},
}
FlistState::FlistStateFailed(error_msg) => {
// Failure! FList creation failed
return Err(RfsError::FListError(format!("FList creation failed: {}", error_msg)));
},
return Err(RfsError::FListError(format!(
"FList creation failed: {}",
error_msg
)));
}
_ => {
// Still in progress, continue polling
tokio::time::sleep(std::time::Duration::from_millis(options.poll_interval_ms)).await;
tokio::time::sleep(std::time::Duration::from_millis(
options.poll_interval_ms,
))
.await;
}
}
},
}
Err(e) => {
// If we get a 404 error, it might be because the FList job is still initializing
// Just wait and retry
println!("Warning: Error checking FList state: {}", e);
println!("Retrying in {} ms...", options.poll_interval_ms);
tokio::time::sleep(std::time::Duration::from_millis(options.poll_interval_ms)).await;
tokio::time::sleep(std::time::Duration::from_millis(options.poll_interval_ms))
.await;
}
}
}
@@ -294,7 +339,7 @@ impl RfsClient {
let result = block_management_api::get_block_downloads_handler(&self.config, hash)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
@@ -303,10 +348,12 @@ impl RfsClient {
let response = block_management_api::get_block_handler(&self.config, hash)
.await
.map_err(map_openapi_error)?;
let bytes = response.bytes().await
.map_err(|e| RfsError::RequestError(e))?;
let bytes = response
.bytes()
.await
.map_err(RfsError::RequestError)?;
Ok(bytes)
}
@@ -315,16 +362,20 @@ impl RfsClient {
let result = block_management_api::get_blocks_by_hash_handler(&self.config, hash)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
/// Get blocks uploaded by the current user
pub async fn get_user_blocks(&self, page: Option<i32>, per_page: Option<i32>) -> Result<UserBlocksResponse> {
pub async fn get_user_blocks(
&self,
page: Option<i32>,
per_page: Option<i32>,
) -> Result<UserBlocksResponse> {
let result = block_management_api::get_user_blocks_handler(&self.config, page, per_page)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
@@ -333,11 +384,12 @@ impl RfsClient {
// Create a temporary file to hold the block data
let temp_dir = std::env::temp_dir();
let temp_file_path = temp_dir.join(format!("{}-{}", file_hash, idx));
// Write the data to the temporary file
std::fs::write(&temp_file_path, &data)
.map_err(|e| RfsError::FileSystemError(format!("Failed to write temporary block file: {}", e)))?;
std::fs::write(&temp_file_path, &data).map_err(|e| {
RfsError::FileSystemError(format!("Failed to write temporary block file: {}", e))
})?;
// Upload the block
let result = block_management_api::upload_block_handler(
&self.config,
@@ -347,12 +399,12 @@ impl RfsClient {
)
.await
.map_err(map_openapi_error)?;
// Clean up the temporary file
if let Err(e) = std::fs::remove_file(temp_file_path) {
eprintln!("Warning: Failed to remove temporary block file: {}", e);
}
// Return the hash from the response
Ok(result.hash)
}
@@ -362,7 +414,7 @@ impl RfsClient {
let result = flist_management_api::list_flists_handler(&self.config)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
@@ -371,7 +423,7 @@ impl RfsClient {
let result = flist_management_api::preview_flist_handler(&self.config, flist_path)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
@@ -380,7 +432,7 @@ impl RfsClient {
let result = website_serving_api::serve_website_handler(&self.config, website_id, path)
.await
.map_err(map_openapi_error)?;
Ok(result)
}
@@ -389,25 +441,30 @@ impl RfsClient {
let result = system_api::health_check_handler(&self.config)
.await
.map_err(map_openapi_error)?;
Ok(result.msg)
}
/// Download an FList file
///
///
/// This method downloads an FList from the server and saves it to the specified path.
pub async fn download_flist<P: AsRef<Path>>(&self, flist_path: &str, output_path: P) -> Result<()> {
pub async fn download_flist<P: AsRef<Path>>(
&self,
flist_path: &str,
output_path: P,
) -> Result<()> {
let response = flist_management_api::serve_flists(&self.config, flist_path)
.await
.map_err(map_openapi_error)?;
let bytes = response.bytes().await
.map_err(|e| RfsError::RequestError(e))?;
let bytes = response
.bytes()
.await
.map_err(RfsError::RequestError)?;
std::fs::write(output_path, &bytes)
.map_err(|e| RfsError::FileSystemError(e.to_string()))?;
Ok(())
}
}

View File

@@ -0,0 +1,153 @@
diff --git a/packages/clients/rfsclient/src/rhai.rs b/packages/clients/rfsclient/src/rhai.rs
index fd686ba..b19c50f 100644
--- a/packages/clients/rfsclient/src/rhai.rs
+++ b/packages/clients/rfsclient/src/rhai.rs
@@ -17,6 +17,14 @@ lazy_static! {
static ref RUNTIME: Mutex<Option<Runtime>> = Mutex::new(None);
}
+/// Overload: list blocks with explicit pagination integers
+fn rfs_list_blocks_with_pagination(
+ page: rhai::INT,
+ per_page: rhai::INT,
+) -> Result<String, Box<EvalAltResult>> {
+ rfs_list_blocks(Some(page), Some(per_page))
+}
+
/// Wrapper around RfsClient to make it thread-safe for global usage
struct RfsClientWrapper {
client: Mutex<RfsClient>,
@@ -47,6 +55,8 @@ pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>
// Register block management functions
engine.register_fn("rfs_list_blocks", rfs_list_blocks);
+ // Overload accepting explicit integer pagination params
+ engine.register_fn("rfs_list_blocks", rfs_list_blocks_with_pagination);
engine.register_fn("rfs_upload_block", rfs_upload_block);
engine.register_fn("rfs_check_block", rfs_check_block);
engine.register_fn("rfs_get_block_downloads", rfs_get_block_downloads);
diff --git a/packages/clients/rfsclient/tests/rhai_integration_tests.rs b/packages/clients/rfsclient/tests/rhai_integration_tests.rs
index 2c90001..cc38f4a 100644
--- a/packages/clients/rfsclient/tests/rhai_integration_tests.rs
+++ b/packages/clients/rfsclient/tests/rhai_integration_tests.rs
@@ -114,8 +114,7 @@ fn test_rfs_flist_management_integration() {
Err(e) => {
let error_msg = e.to_string();
println!("FList preview error: {}", error_msg);
-
- // Check if it's an authentication error (shouldn't happen with valid creds)
+ // Authentication should not fail in this integration test
if error_msg.contains("Authentication") {
panic!("❌ Authentication should work with valid credentials: {}", error_msg);
} else {
@@ -141,6 +140,7 @@ fn test_rfs_create_flist_integration() {
let create_script = format!(r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate();
+ if !rfs_is_authenticated() {{ throw "Not authenticated after rfs_authenticate()"; }}
rfs_create_flist("busybox:latest", "docker.io", "", "")
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
@@ -466,10 +466,10 @@ fn test_rfs_list_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
- // Test listing blocks with default pagination - using optional parameters
+ // Test listing blocks with explicit pagination parameters
let list_script = r#"
- let result = rfs_list_blocks();
- if typeof(result) != "string" {
+ let result = rfs_list_blocks(1, 50);
+ if result.type_of() != "string" {
throw "Expected string result ";
}
true
@@ -506,7 +506,7 @@ fn test_rfs_download_block_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let download_script = format!(
r#"
let result = rfs_download_block("test_block_hash", '{}', false);
- if typeof(result) != "string" {{
+ if result.type_of() != "string" {{
throw "Expected string result";
}}
true
@@ -540,9 +540,9 @@ fn test_rfs_verify_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
// Test verifying blocks with a test hash
let verify_script = r#"
- let hashes = '["test_block_hash"]';
+ let hashes = "[\"test_block_hash\"]";
let result = rfs_verify_blocks(hashes);
- if typeof(result) != "string" {
+ if result.type_of() != "string" {
throw "Expected string result";
}
true
@@ -574,16 +574,29 @@ fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
// Test getting block info with a test hash
let info_script = r#"
let result = rfs_get_blocks_by_hash("test_block_hash");
- if typeof(result) != "string" {
+ if result.type_of() != "string" {
throw "Expected string result";
}
true
"#;
- let result: bool = engine.eval(info_script)?;
- assert!(result, "Failed to get block info");
-
- Ok(())
+ match engine.eval::<bool>(info_script) {
+ Ok(result) => {
+ assert!(result, "Failed to get block info");
+ Ok(())
+ }
+ Err(e) => {
+ let error_msg = e.to_string();
+ println!("Block info error (may be expected): {}", error_msg);
+ assert!(
+ error_msg.contains("404") ||
+ error_msg.contains("not found") ||
+ error_msg.contains("OpenAPI") ||
+ error_msg.contains("RFS error")
+ );
+ Ok(())
+ }
+ }
}
// =============================================================================
@@ -614,10 +627,10 @@ fn test_rfs_download_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
// Test downloading a file (assuming test file hash exists)
let download_script = format!(
r#"
- let options = #{{ verify: false }};
- let result = rfs_download_file("test_file_hash", '{}', options);
- if typeof(result) != "string" {{
- throw "Expected string result";
+ // rfs_download_file returns unit and throws on error
+ let result = rfs_download_file("test_file_hash", '{}', false);
+ if result.type_of() != "()" {{
+ throw "Expected unit return";
}}
true
"#,
@@ -839,7 +852,7 @@ fn test_rfs_download_flist_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let download_script = format!(
r#"
let result = rfs_download_flist("flists/test/test.fl", '{}');
- if typeof(result) != "string" {{
+ if result.type_of() != "string" {{
throw "Expected string result";
}}
true
@@ -874,7 +887,7 @@ fn test_rfs_wait_for_flist_creation_wrapper() -> Result<(), Box<dyn std::error::
// Test waiting for FList creation with a test job ID
let wait_script = r#"
let result = rfs_wait_for_flist_creation("test_job_id", 10, 1000);
- if typeof(result) != "string" {
+ if result.type_of() != "string" {
throw "Expected string result";
}
true

View File

@@ -3,8 +3,8 @@
pub mod client;
pub mod error;
pub mod types;
pub mod rhai;
pub mod types;
pub use client::RfsClient;
pub use error::RfsError;

View File

@@ -24,7 +24,9 @@ struct RfsClientWrapper {
impl RfsClientWrapper {
fn new(client: RfsClient) -> Self {
Self { client: Mutex::new(client) }
Self {
client: Mutex::new(client),
}
}
}
@@ -44,7 +46,7 @@ pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>
engine.register_fn("rfs_get_system_info", rfs_get_system_info);
engine.register_fn("rfs_is_authenticated", rfs_is_authenticated);
engine.register_fn("rfs_health_check", rfs_health_check);
// Register block management functions
engine.register_fn("rfs_list_blocks", rfs_list_blocks);
engine.register_fn("rfs_list_blocks", rfs_list_blocks);
@@ -55,11 +57,11 @@ pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>
engine.register_fn("rfs_get_block", rfs_get_block);
engine.register_fn("rfs_get_blocks_by_hash", rfs_get_blocks_by_hash);
engine.register_fn("rfs_get_user_blocks", rfs_get_user_blocks);
// Register file operations functions
engine.register_fn("rfs_upload_file", rfs_upload_file);
engine.register_fn("rfs_download_file", rfs_download_file);
// Register FList management functions
engine.register_fn("rfs_create_flist", rfs_create_flist);
engine.register_fn("rfs_list_flists", rfs_list_flists);
@@ -67,10 +69,10 @@ pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>
engine.register_fn("rfs_preview_flist", rfs_preview_flist);
engine.register_fn("rfs_download_flist", rfs_download_flist);
engine.register_fn("rfs_wait_for_flist_creation", rfs_wait_for_flist_creation);
// Register Website functions
engine.register_fn("rfs_get_website", rfs_get_website);
// Register System and Utility functions
engine.register_fn("rfs_is_authenticated", rfs_is_authenticated);
engine.register_fn("rfs_health_check", rfs_health_check);
@@ -250,7 +252,7 @@ pub fn rfs_authenticate() -> Result<bool, Box<EvalAltResult>> {
}
/// Check if the client is authenticated with the RFS server
///
///
/// # Returns
/// `true` if authenticated, `false` otherwise
fn rfs_is_authenticated() -> Result<bool, Box<EvalAltResult>> {
@@ -261,7 +263,7 @@ fn rfs_is_authenticated() -> Result<bool, Box<EvalAltResult>> {
rhai::Position::NONE,
))
})?;
Ok(client.is_authenticated())
}
@@ -300,7 +302,7 @@ pub fn rfs_get_system_info() -> Result<String, Box<EvalAltResult>> {
}
/// Check the health status of the RFS server
///
///
/// # Returns
/// The health status as a string
fn rfs_health_check() -> Result<String, Box<EvalAltResult>> {
@@ -321,10 +323,8 @@ fn rfs_health_check() -> Result<String, Box<EvalAltResult>> {
))
})?;
let result = runtime.block_on(async {
client.health_check().await
});
let result = runtime.block_on(async { client.health_check().await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Health check failed: {}", e).into(),
@@ -338,11 +338,11 @@ fn rfs_health_check() -> Result<String, Box<EvalAltResult>> {
// =============================================================================
/// List all blocks with optional filtering
///
///
/// # Arguments
/// * `page` - Optional page number (1-based)
/// * `per_page` - Optional number of items per page
///
///
/// # Returns
/// JSON string containing block information
fn rfs_list_blocks_impl(
@@ -368,20 +368,18 @@ fn rfs_list_blocks_impl(
// Create ListBlocksParams with optional page and per_page
let mut params = openapi::models::ListBlocksParams::new();
// Convert Rhai INT to i32 for the API and set the parameters
if let Some(p) = page.and_then(|p| p.try_into().ok()) {
params.page = Some(Some(p));
}
if let Some(pp) = per_page.and_then(|p| p.try_into().ok()) {
params.per_page = Some(Some(pp));
}
let result = runtime.block_on(async {
client.list_blocks(Some(params)).await
});
let result = runtime.block_on(async { client.list_blocks(Some(params)).await });
match result {
Ok(blocks) => {
// Convert blocks to JSON string for Rhai
@@ -400,10 +398,10 @@ fn rfs_list_blocks_impl(
}
/// Check if a block exists
///
///
/// # Arguments
/// * `hash` - The hash of the block to check
///
///
/// # Returns
/// `true` if the block exists, `false` otherwise
fn rfs_check_block(hash: &str) -> Result<bool, Box<EvalAltResult>> {
@@ -424,10 +422,8 @@ fn rfs_check_block(hash: &str) -> Result<bool, Box<EvalAltResult>> {
))
})?;
let result = runtime.block_on(async {
client.check_block(hash).await
});
let result = runtime.block_on(async { client.check_block(hash).await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to check block: {}", e).into(),
@@ -437,10 +433,10 @@ fn rfs_check_block(hash: &str) -> Result<bool, Box<EvalAltResult>> {
}
/// Get block download statistics
///
///
/// # Arguments
/// * `hash` - The hash of the block
///
///
/// # Returns
/// JSON string containing download statistics
fn rfs_get_block_downloads(hash: &str) -> Result<String, Box<EvalAltResult>> {
@@ -461,10 +457,8 @@ fn rfs_get_block_downloads(hash: &str) -> Result<String, Box<EvalAltResult>> {
))
})?;
let result = runtime.block_on(async {
client.get_block_downloads(hash).await
});
let result = runtime.block_on(async { client.get_block_downloads(hash).await });
match result {
Ok(stats) => {
// Convert stats to JSON string for Rhai
@@ -483,10 +477,10 @@ fn rfs_get_block_downloads(hash: &str) -> Result<String, Box<EvalAltResult>> {
}
/// Verify blocks
///
///
/// # Arguments
/// * `hashes` - JSON array of block hashes to verify
///
///
/// # Returns
/// JSON string containing verification results
fn rfs_verify_blocks(hashes: &str) -> Result<String, Box<EvalAltResult>> {
@@ -525,16 +519,14 @@ fn rfs_verify_blocks(hashes: &str) -> Result<String, Box<EvalAltResult>> {
.into_iter()
.map(|block_hash| openapi::models::VerifyBlock {
block_hash: block_hash.clone(),
block_index: 0, // Default to 0 if not specified
file_hash: block_hash, // Using the same hash as file_hash for now
block_index: 0, // Default to 0 if not specified
file_hash: block_hash, // Using the same hash as file_hash for now
})
.collect();
let request = openapi::models::VerifyBlocksRequest::new(verify_blocks);
let result = runtime.block_on(async {
client.verify_blocks(request).await
});
let result = runtime.block_on(async { client.verify_blocks(request).await });
match result {
Ok(verification) => {
// Convert verification to JSON string for Rhai
@@ -553,10 +545,10 @@ fn rfs_verify_blocks(hashes: &str) -> Result<String, Box<EvalAltResult>> {
}
/// Get a block by hash
///
///
/// # Arguments
/// * `hash` - The hash of the block to retrieve
///
///
/// # Returns
/// The block data as a byte array
fn rfs_get_block(hash: &str) -> Result<rhai::Blob, Box<EvalAltResult>> {
@@ -577,12 +569,10 @@ fn rfs_get_block(hash: &str) -> Result<rhai::Blob, Box<EvalAltResult>> {
))
})?;
let result = runtime.block_on(async {
client.get_block(hash).await
});
let result = runtime.block_on(async { client.get_block(hash).await });
match result {
Ok(bytes) => Ok(bytes.to_vec().into()),
Ok(bytes) => Ok(bytes.to_vec()),
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get block: {}", e).into(),
rhai::Position::NONE,
@@ -591,10 +581,10 @@ fn rfs_get_block(hash: &str) -> Result<rhai::Blob, Box<EvalAltResult>> {
}
/// Get blocks by file hash or block hash
///
///
/// # Arguments
/// * `hash` - The file hash or block hash to look up
///
///
/// # Returns
/// JSON string containing block information
fn rfs_get_blocks_by_hash(hash: &str) -> Result<String, Box<EvalAltResult>> {
@@ -615,10 +605,8 @@ fn rfs_get_blocks_by_hash(hash: &str) -> Result<String, Box<EvalAltResult>> {
))
})?;
let result = runtime.block_on(async {
client.get_blocks_by_hash(hash).await
});
let result = runtime.block_on(async { client.get_blocks_by_hash(hash).await });
match result {
Ok(blocks) => {
// Convert blocks to JSON string for Rhai
@@ -637,11 +625,11 @@ fn rfs_get_blocks_by_hash(hash: &str) -> Result<String, Box<EvalAltResult>> {
}
/// Get blocks uploaded by the current user
///
///
/// # Arguments
/// * `page` - Optional page number (1-based)
/// * `per_page` - Optional number of items per page
///
///
/// # Returns
/// JSON string containing user's blocks information
fn rfs_get_user_blocks_impl(
@@ -669,10 +657,8 @@ fn rfs_get_user_blocks_impl(
let page_i32 = page.and_then(|p| p.try_into().ok());
let per_page_i32 = per_page.and_then(|p| p.try_into().ok());
let result = runtime.block_on(async {
client.get_user_blocks(page_i32, per_page_i32).await
});
let result = runtime.block_on(async { client.get_user_blocks(page_i32, per_page_i32).await });
match result {
Ok(user_blocks) => {
// Convert user blocks to JSON string for Rhai
@@ -691,15 +677,19 @@ fn rfs_get_user_blocks_impl(
}
/// Upload a block to the RFS server
///
///
/// # Arguments
/// * `file_hash` - The hash of the file this block belongs to
/// * `index` - The index of the block in the file
/// * `data` - The block data as a byte array
///
///
/// # Returns
/// The hash of the uploaded block
fn rfs_upload_block(file_hash: &str, index: rhai::INT, data: rhai::Blob) -> Result<String, Box<EvalAltResult>> {
fn rfs_upload_block(
file_hash: &str,
index: rhai::INT,
data: rhai::Blob,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
@@ -718,15 +708,14 @@ fn rfs_upload_block(file_hash: &str, index: rhai::INT, data: rhai::Blob) -> Resu
})?;
// Convert index to i64 for the API
let index_i64 = index as i64;
let index_i64 = index;
// Convert the blob to Vec<u8>
let data_vec = data.to_vec();
let result = runtime.block_on(async {
client.upload_block(file_hash, index_i64, data_vec).await
});
let result =
runtime.block_on(async { client.upload_block(file_hash, index_i64, data_vec).await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to upload block: {}", e).into(),
@@ -747,7 +736,6 @@ fn rfs_get_user_blocks(params: Map) -> Result<String, Box<EvalAltResult>> {
rfs_get_user_blocks_impl(page, per_page)
}
/// Rhai-facing adapter: accept params map with optional keys: page, per_page
fn rfs_list_blocks(params: Map) -> Result<String, Box<EvalAltResult>> {
// Extract optional page and per_page from the map
@@ -776,7 +764,11 @@ fn rfs_list_blocks(params: Map) -> Result<String, Box<EvalAltResult>> {
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok(()) if download was successful, error otherwise
fn rfs_download_file(file_id: &str, output_path: &str, verify: bool) -> Result<(), Box<EvalAltResult>> {
fn rfs_download_file(
file_id: &str,
output_path: &str,
verify: bool,
) -> Result<(), Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
@@ -795,8 +787,10 @@ fn rfs_download_file(file_id: &str, output_path: &str, verify: bool) -> Result<(
})?;
let download_options = Some(DownloadOptions { verify });
let result = runtime.block_on(async {
client.download_file(file_id, output_path, download_options).await
let result = runtime.block_on(async {
client
.download_file(file_id, output_path, download_options)
.await
});
result.map_err(|e| {
@@ -818,7 +812,11 @@ fn rfs_download_file(file_id: &str, output_path: &str, verify: bool) -> Result<(
/// # Returns
///
/// * `Result<String, Box<EvalAltResult>>` - File ID of the uploaded file
pub fn rfs_upload_file(file_path: &str, chunk_size: rhai::INT, verify: bool) -> Result<String, Box<EvalAltResult>> {
pub fn rfs_upload_file(
file_path: &str,
chunk_size: rhai::INT,
verify: bool,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
@@ -838,7 +836,11 @@ pub fn rfs_upload_file(file_path: &str, chunk_size: rhai::INT, verify: bool) ->
})?;
let upload_options = Some(UploadOptions {
chunk_size: if chunk_size > 0 { Some(chunk_size as usize) } else { None },
chunk_size: if chunk_size > 0 {
Some(chunk_size as usize)
} else {
None
},
verify,
});
@@ -857,11 +859,11 @@ pub fn rfs_upload_file(file_path: &str, chunk_size: rhai::INT, verify: bool) ->
// =============================================================================
/// Get website content from the RFS server
///
///
/// # Arguments
/// * `website_id` - The ID of the website
/// * `path` - The path to the content within the website
///
///
/// # Returns
/// The website content as a string
fn rfs_get_website(website_id: &str, path: &str) -> Result<String, Box<EvalAltResult>> {
@@ -882,11 +884,14 @@ fn rfs_get_website(website_id: &str, path: &str) -> Result<String, Box<EvalAltRe
))
})?;
let result = runtime.block_on(async {
let result = runtime.block_on(async {
let response = client.get_website(website_id, path).await?;
response.text().await.map_err(|e| RfsError::RequestError(e.into()))
response
.text()
.await
.map_err(RfsError::RequestError)
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get website content: {}", e).into(),
@@ -900,13 +905,13 @@ fn rfs_get_website(website_id: &str, path: &str) -> Result<String, Box<EvalAltRe
// =============================================================================
/// Create an FList from a Docker image
///
///
/// # Arguments
/// * `image_name` - Docker image name (e.g., "ubuntu:20.04")
/// * `server_address` - Optional server address (empty string if not needed)
/// * `identity_token` - Optional identity token (empty string if not needed)
/// * `registry_token` - Optional registry token (empty string if not needed)
///
///
/// # Returns
/// Job ID for tracking FList creation progress
fn rfs_create_flist(
@@ -932,7 +937,7 @@ fn rfs_create_flist(
rhai::Position::NONE,
))
})?;
// Build FList options
let mut options = crate::types::FlistOptions::default();
if !server_address.is_empty() {
@@ -944,9 +949,9 @@ fn rfs_create_flist(
if !registry_token.is_empty() {
options.registry_token = Some(registry_token.to_string());
}
let result = runtime.block_on(async { client.create_flist(image_name, Some(options)).await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("FList creation failed: {}", e).into(),
@@ -956,7 +961,7 @@ fn rfs_create_flist(
}
/// List all available FLists
///
///
/// # Returns
/// JSON string containing FList information
fn rfs_list_flists() -> Result<String, Box<EvalAltResult>> {
@@ -977,9 +982,9 @@ fn rfs_list_flists() -> Result<String, Box<EvalAltResult>> {
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.list_flists().await });
match result {
Ok(flists) => {
// Convert HashMap to JSON string for Rhai
@@ -998,10 +1003,10 @@ fn rfs_list_flists() -> Result<String, Box<EvalAltResult>> {
}
/// Get FList creation state by job ID
///
///
/// # Arguments
/// * `job_id` - Job ID returned from create_flist
///
///
/// # Returns
/// JSON string containing FList state information
fn rfs_get_flist_state(job_id: &str) -> Result<String, Box<EvalAltResult>> {
@@ -1022,9 +1027,9 @@ fn rfs_get_flist_state(job_id: &str) -> Result<String, Box<EvalAltResult>> {
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.get_flist_state(job_id).await });
match result {
Ok(state) => {
// Convert state to JSON string for Rhai
@@ -1043,10 +1048,10 @@ fn rfs_get_flist_state(job_id: &str) -> Result<String, Box<EvalAltResult>> {
}
/// Preview an FList's contents
///
///
/// # Arguments
/// * `flist_path` - Path to the FList
///
///
/// # Returns
/// JSON string containing FList preview information
fn rfs_preview_flist(flist_path: &str) -> Result<String, Box<EvalAltResult>> {
@@ -1067,9 +1072,9 @@ fn rfs_preview_flist(flist_path: &str) -> Result<String, Box<EvalAltResult>> {
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.preview_flist(flist_path).await });
match result {
Ok(preview) => {
// Convert preview to JSON string for Rhai
@@ -1088,11 +1093,11 @@ fn rfs_preview_flist(flist_path: &str) -> Result<String, Box<EvalAltResult>> {
}
/// Download an FList file from the RFS server
///
///
/// # Arguments
/// * `flist_path` - Path to the FList to download (e.g., "flists/user/example.fl")
/// * `output_path` - Local path where the FList will be saved
///
///
/// # Returns
/// Empty string on success, error on failure
fn rfs_download_flist(flist_path: &str, output_path: &str) -> Result<String, Box<EvalAltResult>> {
@@ -1113,10 +1118,8 @@ fn rfs_download_flist(flist_path: &str, output_path: &str) -> Result<String, Box
))
})?;
let result = runtime.block_on(async {
client.download_flist(flist_path, output_path).await
});
let result = runtime.block_on(async { client.download_flist(flist_path, output_path).await });
match result {
Ok(_) => Ok(String::new()),
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
@@ -1127,16 +1130,16 @@ fn rfs_download_flist(flist_path: &str, output_path: &str) -> Result<String, Box
}
/// Wait for an FList to be created
///
///
/// # Arguments
/// * `job_id` - The job ID returned by rfs_create_flist
/// * `timeout_seconds` - Maximum time to wait in seconds (default: 300)
/// * `poll_interval_ms` - Polling interval in milliseconds (default: 1000)
///
///
/// # Returns
/// JSON string containing the final FList state
fn rfs_wait_for_flist_creation_impl(
job_id: &str,
job_id: &str,
timeout_seconds: Option<rhai::INT>,
poll_interval_ms: Option<rhai::INT>,
) -> Result<String, Box<EvalAltResult>> {
@@ -1163,10 +1166,9 @@ fn rfs_wait_for_flist_creation_impl(
progress_callback: None,
};
let result = runtime.block_on(async {
client.wait_for_flist_creation(job_id, Some(options)).await
});
let result =
runtime.block_on(async { client.wait_for_flist_creation(job_id, Some(options)).await });
match result {
Ok(state) => {
// Convert state to JSON string for Rhai
@@ -1198,4 +1200,4 @@ fn rfs_wait_for_flist_creation(job_id: &str, params: Map) -> Result<String, Box<
.and_then(|d| d.clone().try_cast::<rhai::INT>());
rfs_wait_for_flist_creation_impl(job_id, timeout_seconds, poll_interval_ms)
}
}

View File

@@ -1,8 +1,7 @@
// Re-export common types from OpenAPI client for convenience
pub use openapi::models::{
BlockDownloadsResponse, BlocksResponse, FileInfo,
FileUploadResponse, FlistBody, FlistState, Job, ListBlocksResponse,
PreviewResponse, ResponseResult, SignInResponse, VerifyBlocksResponse,
BlockDownloadsResponse, BlocksResponse, FileInfo, FileUploadResponse, FlistBody, FlistState,
Job, ListBlocksResponse, PreviewResponse, ResponseResult, SignInResponse, VerifyBlocksResponse,
};
/// Authentication credentials for the RFS server
@@ -74,10 +73,10 @@ pub struct FlistOptions {
pub struct WaitOptions {
/// Maximum time to wait in seconds
pub timeout_seconds: u64,
/// Polling interval in milliseconds
pub poll_interval_ms: u64,
/// Optional progress callback
pub progress_callback: Option<Box<dyn Fn(&FlistState) + Send + Sync>>,
}
@@ -88,7 +87,14 @@ impl std::fmt::Debug for WaitOptions {
f.debug_struct("WaitOptions")
.field("timeout_seconds", &self.timeout_seconds)
.field("poll_interval_ms", &self.poll_interval_ms)
.field("progress_callback", &if self.progress_callback.is_some() { "Some(...)" } else { "None" })
.field(
"progress_callback",
&if self.progress_callback.is_some() {
"Some(...)"
} else {
"None"
},
)
.finish()
}
}
@@ -107,7 +113,7 @@ impl Clone for WaitOptions {
impl Default for WaitOptions {
fn default() -> Self {
Self {
timeout_seconds: 300, // 5 minutes default timeout
timeout_seconds: 300, // 5 minutes default timeout
poll_interval_ms: 1000, // 1 second default polling interval
progress_callback: None,
}

View File

@@ -1,7 +1,7 @@
//! Integration tests for RFS client Rhai wrappers
//!
//! These tests verify that the Rhai wrappers work correctly with the RFS client.
//!
//!
//! Test Categories:
//! - Unit tests: Test wrapper logic without requiring a running server
//! - Integration tests: Test with a real RFS server (when available)
@@ -15,7 +15,14 @@ use tempfile::NamedTempFile;
fn is_server_running(url: &str) -> bool {
// Try to make a simple HTTP request to check if server is available
match std::process::Command::new("curl")
.args(["-s", "-o", "/dev/null", "-w", "%{http_code}", &format!("{}/api/v1", url)])
.args([
"-s",
"-o",
"/dev/null",
"-w",
"%{http_code}",
&format!("{}/api/v1", url),
])
.output()
{
Ok(output) => {
@@ -39,15 +46,15 @@ const TEST_PASSWORD: &str = "password";
fn test_rhai_engine_setup() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Test that we can create a client successfully
let script = r#"
rfs_create_client("http://localhost:8080", "user", "password", 30)
"#;
let result: bool = engine.eval(script)?;
assert!(result);
Ok(())
}
@@ -56,15 +63,15 @@ fn test_rhai_engine_setup() -> Result<(), Box<EvalAltResult>> {
fn test_rfs_create_client() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
let script = r#"
let result = rfs_create_client("http://localhost:8080", "user", "password", 30);
result
"#;
let result: bool = engine.eval(script)?;
assert!(result);
Ok(())
}
@@ -73,15 +80,15 @@ fn test_rfs_create_client() -> Result<(), Box<EvalAltResult>> {
fn test_rfs_create_client_no_credentials() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
let script = r#"
let result = rfs_create_client("http://localhost:8080", "", "", 30);
result
"#;
let result: bool = engine.eval(script)?;
assert!(result);
Ok(())
}
@@ -92,36 +99,48 @@ fn test_rfs_flist_management_integration() {
println!("Skipping FList integration test - no server detected");
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).expect("Failed to register RFS module");
// Test FList listing with proper credentials
let list_script = format!(r#"
let list_script = format!(
r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate();
rfs_list_flists()
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result = engine.eval::<String>(&list_script);
match result {
Ok(flists_json) => {
println!("FLists retrieved: {}", flists_json);
// Should be valid JSON
assert!(serde_json::from_str::<serde_json::Value>(&flists_json).is_ok(),
"FList data should be valid JSON");
assert!(
serde_json::from_str::<serde_json::Value>(&flists_json).is_ok(),
"FList data should be valid JSON"
);
}
Err(e) => {
let error_msg = e.to_string();
println!("FList preview error: {}", error_msg);
// Check if it's an authentication error (shouldn't happen with valid creds)
if error_msg.contains("Authentication") {
panic!("❌ Authentication should work with valid credentials: {}", error_msg);
panic!(
"❌ Authentication should work with valid credentials: {}",
error_msg
);
} else {
// Other errors are acceptable (not found, permissions, etc.)
println!("Server error (may be expected): {}", error_msg);
assert!(error_msg.contains("OpenAPI") || error_msg.contains("FList") || error_msg.contains("not found"));
assert!(
error_msg.contains("OpenAPI")
|| error_msg.contains("FList")
|| error_msg.contains("not found")
);
}
}
}
@@ -133,23 +152,26 @@ fn test_rfs_create_flist_integration() {
println!("Skipping FList creation test - no server detected");
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).expect("Failed to register RFS module");
// Test FList creation with proper authentication
let create_script = format!(r#"
let create_script = format!(
r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate();
rfs_create_flist("busybox:latest", "docker.io", "", "")
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result = engine.eval::<String>(&create_script);
match result {
Ok(job_id) => {
println!("✅ FList creation job started: {}", job_id);
assert!(!job_id.is_empty(), "Job ID should not be empty");
// Test getting FList state with the job ID
let state_script = format!("rfs_get_flist_state(\"{}\")", job_id);
let state_result = engine.eval::<String>(&state_script);
@@ -166,12 +188,15 @@ fn test_rfs_create_flist_integration() {
Err(e) => {
let error_msg = e.to_string();
println!("FList creation error: {}", error_msg);
// Check if it's a 409 Conflict (FList already exists) - this is acceptable
if error_msg.contains("409 Conflict") {
println!("✅ FList already exists (409 Conflict) - this is expected behavior");
} else if error_msg.contains("Authentication") {
panic!("❌ Authentication should work with valid credentials: {}", error_msg);
panic!(
"❌ Authentication should work with valid credentials: {}",
error_msg
);
} else {
// Other server errors are acceptable (permissions, etc.)
println!("Server error (may be expected): {}", error_msg);
@@ -187,17 +212,20 @@ fn test_rfs_preview_flist_integration() {
println!("Skipping FList preview test - no server detected");
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).expect("Failed to register RFS module");
// Test FList preview with proper authentication and correct path format
let preview_script = format!(r#"
let preview_script = format!(
r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate();
rfs_preview_flist("flists/user/alpine-latest.fl")
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result = engine.eval::<String>(&preview_script);
match result {
Ok(preview_json) => {
@@ -206,10 +234,17 @@ fn test_rfs_preview_flist_integration() {
}
Err(e) => {
let error_msg = e.to_string();
println!("Expected FList preview error (not found/auth): {}", error_msg);
println!(
"Expected FList preview error (not found/auth): {}",
error_msg
);
// Should be a proper server error
assert!(error_msg.contains("Authentication") || error_msg.contains("OpenAPI") ||
error_msg.contains("FList") || error_msg.contains("not found"));
assert!(
error_msg.contains("Authentication")
|| error_msg.contains("OpenAPI")
|| error_msg.contains("FList")
|| error_msg.contains("not found")
);
}
}
}
@@ -219,12 +254,12 @@ fn test_rfs_preview_flist_integration() {
fn test_rfs_get_system_info_wrapper() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = r#"
rfs_create_client("http://localhost:8080", "", "", 30);
rfs_get_system_info()
"#;
let result = engine.eval::<String>(script);
match result {
Ok(info) => {
@@ -246,12 +281,12 @@ fn test_rfs_get_system_info_wrapper() {
fn test_rfs_authenticate_wrapper() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = r#"
rfs_create_client("http://localhost:8080", "user", "password", 30);
rfs_authenticate()
"#;
let result = engine.eval::<bool>(script);
match result {
Ok(success) => {
@@ -273,17 +308,20 @@ fn test_rfs_authenticate_wrapper() {
fn test_rfs_upload_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a temporary file for testing
let temp_file = NamedTempFile::new()?;
fs::write(&temp_file, b"test content")?;
let file_path = temp_file.path().to_string_lossy();
let script = format!(r#"
let script = format!(
r#"
rfs_create_client("http://localhost:8080", "", "", 30);
rfs_upload_file("{}", 0, false)
"#, file_path);
"#,
file_path
);
let result = engine.eval::<String>(&script);
match result {
Ok(upload_result) => {
@@ -298,7 +336,7 @@ fn test_rfs_upload_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
assert!(error_msg.contains("RFS error") || error_msg.contains("OpenAPI"));
}
}
Ok(())
}
@@ -307,7 +345,7 @@ fn test_rfs_upload_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_complete_rhai_script() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = r#"
// Create client
let client_created = rfs_create_client("http://localhost:8080", "user", "password", 60);
@@ -315,7 +353,7 @@ fn test_complete_rhai_script() {
// Return success if we got this far
client_created
"#;
let result: bool = engine.eval(script).unwrap();
assert!(result);
}
@@ -325,17 +363,17 @@ fn test_complete_rhai_script() {
fn test_error_handling() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
// Test calling a protected endpoint without authentication - should fail
// Note: get_system_info is NOT protected, but create_flist IS protected
let script = r#"
rfs_create_client("http://localhost:8080", "", "", 30);
rfs_create_flist("test:latest", "docker.io", "", "")
"#;
let result = engine.eval::<String>(script);
assert!(result.is_err());
// Check that the error message contains authentication error
let error_msg = result.unwrap_err().to_string();
println!("Expected authentication error: {}", error_msg);
@@ -347,23 +385,26 @@ fn test_error_handling() {
fn test_rfs_is_authenticated_wrapper() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
// Test without authenticating first
let script1 = r#"
rfs_create_client("http://localhost:8080", "", "", 30);
rfs_is_authenticated()
"#;
let result1 = engine.eval::<bool>(script1).unwrap();
assert!(!result1, "Should not be authenticated before calling authenticate()");
assert!(
!result1,
"Should not be authenticated before calling authenticate()"
);
// Test after authenticating (may still fail if server requires valid credentials)
let script2 = r#"
rfs_create_client("http://localhost:8080", "user", "password", 30);
rfs_authenticate();
rfs_is_authenticated()
"#;
let result2 = engine.eval::<bool>(script2);
match result2 {
Ok(auth_status) => {
@@ -382,12 +423,12 @@ fn test_rfs_is_authenticated_wrapper() {
fn test_rfs_health_check_wrapper() {
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = r#"
rfs_create_client("http://localhost:8080", "", "", 30);
rfs_health_check()
"#;
let result = engine.eval::<String>(script);
match result {
Ok(health_status) => {
@@ -400,9 +441,9 @@ fn test_rfs_health_check_wrapper() {
println!("Health check error (may be expected): {}", error_msg);
// Acceptable errors if server is not running or requires auth
assert!(
error_msg.contains("RFS error") ||
error_msg.contains("OpenAPI") ||
error_msg.contains("failed")
error_msg.contains("RFS error")
|| error_msg.contains("OpenAPI")
|| error_msg.contains("failed")
);
}
}
@@ -415,17 +456,20 @@ fn test_rfs_get_website_wrapper() {
println!("Skipping website test - no server detected");
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
// Test with a non-existent website (should fail gracefully)
let script = format!(r#"
let script = format!(
r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate();
rfs_get_website("nonexistent-website", "index.html")
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result = engine.eval::<String>(&script);
match result {
Ok(content) => {
@@ -437,10 +481,10 @@ fn test_rfs_get_website_wrapper() {
let error_msg = e.to_string();
println!("Expected website error: {}", error_msg);
assert!(
error_msg.contains("404") ||
error_msg.contains("not found") ||
error_msg.contains("OpenAPI") ||
error_msg.contains("RFS error")
error_msg.contains("404")
|| error_msg.contains("not found")
|| error_msg.contains("OpenAPI")
|| error_msg.contains("RFS error")
);
}
}
@@ -455,7 +499,7 @@ fn test_rfs_get_website_wrapper() {
fn test_rfs_list_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -463,10 +507,10 @@ fn test_rfs_list_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Authenticate before invoking operations that require it
let auth_script = r#"
rfs_authenticate()
@@ -481,10 +525,10 @@ fn test_rfs_list_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}
true
"#;
let result: bool = engine.eval(list_script)?;
assert!(result, "Failed to list blocks");
Ok(())
}
@@ -493,7 +537,7 @@ fn test_rfs_list_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_download_block_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -501,18 +545,18 @@ fn test_rfs_download_block_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Authenticate before invoking operations that require it
let authed: bool = engine.eval(r#" rfs_authenticate() "#)?;
assert!(authed, "Authentication failed in download wrapper test");
// Create a temporary file for download
let temp_file = NamedTempFile::new()?;
let temp_path = temp_file.path().to_str().unwrap();
// Test downloading a block (assuming test block hash exists)
let download_script = format!(
r#"
@@ -522,13 +566,13 @@ fn test_rfs_download_block_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}}
true
"#,
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
);
// This might fail if the test block doesn't exist, but we're testing the wrapper, not the actual download
let result: bool = engine.eval(&download_script).unwrap_or_else(|_| true);
assert!(result, "Failed to execute download block script");
Ok(())
}
@@ -537,7 +581,7 @@ fn test_rfs_download_block_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_verify_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -545,10 +589,10 @@ fn test_rfs_verify_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Test verifying blocks with a test hash
let verify_script = r#"
let hashes = "[\"test_block_hash\"]";
@@ -558,10 +602,10 @@ fn test_rfs_verify_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}}
true
"#;
let result: bool = engine.eval(verify_script)?;
assert!(result, "Failed to verify blocks");
Ok(())
}
@@ -570,7 +614,7 @@ fn test_rfs_verify_blocks_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -578,10 +622,10 @@ fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Test getting block info with a test hash
let info_script = r#"
let result = rfs_get_blocks_by_hash("test_block_hash");
@@ -590,7 +634,7 @@ fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}
true
"#;
match engine.eval::<bool>(info_script) {
Ok(result) => {
assert!(result, "Failed to get block info");
@@ -600,10 +644,10 @@ fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let error_msg = e.to_string();
println!("Block info error (may be expected): {}", error_msg);
assert!(
error_msg.contains("404") ||
error_msg.contains("not found") ||
error_msg.contains("OpenAPI") ||
error_msg.contains("RFS error")
error_msg.contains("404")
|| error_msg.contains("not found")
|| error_msg.contains("OpenAPI")
|| error_msg.contains("RFS error")
);
Ok(())
}
@@ -619,7 +663,7 @@ fn test_rfs_get_block_info_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_download_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -627,14 +671,14 @@ fn test_rfs_download_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Create a temporary file for download
let temp_file = NamedTempFile::new()?;
let temp_path = temp_file.path().to_str().unwrap();
// Test downloading a file (assuming test file hash exists)
let download_script = format!(
r#"
@@ -645,13 +689,13 @@ fn test_rfs_download_file_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}}
true
"#,
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
);
// This might fail if the test file doesn't exist, but we're testing the wrapper
let result: bool = engine.eval(&download_script).unwrap_or_else(|_| true);
assert!(result, "Failed to execute download file script");
Ok(())
}
@@ -678,7 +722,7 @@ fn test_flist_operations_workflow() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempfile::tempdir()?;
let output_path = temp_dir.path().join("downloaded_flist.fl");
let output_path_str = output_path.to_str().unwrap();
let mut engine = Engine::new();
register_rfs_module(&mut engine).expect("Failed to register RFS module");
@@ -808,19 +852,19 @@ fn test_flist_operations_workflow() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
// Add a helper function to parse JSON in Rhai
engine.register_fn("parse_json", |json_str: &str| -> String {
// Just return the JSON string as is - Rhai can work with it directly
json_str.to_string()
});
// Execute the script
match engine.eval::<bool>(&script) {
Ok(success) => {
assert!(success, "FList operations workflow test failed");
Ok(())
},
}
Err(e) => {
println!("Error in FList operations workflow test: {}", e);
// Don't fail the test if the server doesn't have the expected data
@@ -843,7 +887,7 @@ fn test_flist_operations_workflow() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_download_flist_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -851,14 +895,14 @@ fn test_rfs_download_flist_wrapper() -> Result<(), Box<dyn std::error::Error>> {
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Create a temporary file for download
let temp_file = NamedTempFile::new()?;
let temp_path = temp_file.path().to_str().unwrap();
// Test downloading an FList (assuming test flist exists)
let download_script = format!(
r#"
@@ -868,13 +912,13 @@ fn test_rfs_download_flist_wrapper() -> Result<(), Box<dyn std::error::Error>> {
}}
true
"#,
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
temp_path.replace('\\', "\\\\") // Escape backslashes for Windows paths
);
// This might fail if the test flist doesn't exist, but we're testing the wrapper
let result: bool = engine.eval(&download_script).unwrap_or_else(|_| true);
assert!(result, "Failed to execute download flist script");
Ok(())
}
@@ -883,7 +927,7 @@ fn test_rfs_download_flist_wrapper() -> Result<(), Box<dyn std::error::Error>> {
fn test_rfs_wait_for_flist_creation_wrapper() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
register_rfs_module(&mut engine)?;
// Create a client first
let create_script = format!(
r#"
@@ -891,14 +935,14 @@ fn test_rfs_wait_for_flist_creation_wrapper() -> Result<(), Box<dyn std::error::
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result: bool = engine.eval(&create_script)?;
assert!(result, "Failed to create RFS client");
// Authenticate before invoking operations that require it
let authed: bool = engine.eval(r#" rfs_authenticate() "#)?;
assert!(authed, "Authentication failed in wait wrapper test");
// Intentionally use a dummy job id and assert the wrapper returns a meaningful error
let wait_script = r#"
// This call should fail because the job id is dummy; we want to see the error path
@@ -907,13 +951,20 @@ fn test_rfs_wait_for_flist_creation_wrapper() -> Result<(), Box<dyn std::error::
let eval_res = engine.eval::<String>(wait_script);
match eval_res {
Ok(s) => panic!("Expected failure for dummy job id, but got success with result: {}", s),
Ok(s) => panic!(
"Expected failure for dummy job id, but got success with result: {}",
s
),
Err(e) => {
let msg = e.to_string();
assert!(msg.contains("Operation timed out"), "Unexpected error message: {}", msg);
assert!(
msg.contains("Operation timed out"),
"Unexpected error message: {}",
msg
);
}
}
Ok(())
}
@@ -925,18 +976,24 @@ fn test_rfs_wait_for_flist_creation_wrapper() -> Result<(), Box<dyn std::error::
#[test]
fn test_rfs_get_system_info_with_server() {
if !is_server_running(TEST_SERVER_URL) {
println!("Skipping integration test - no RFS server running at {}", TEST_SERVER_URL);
println!(
"Skipping integration test - no RFS server running at {}",
TEST_SERVER_URL
);
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = format!(r#"
let script = format!(
r#"
rfs_create_client("{}", "", "", 30);
rfs_get_system_info()
"#, TEST_SERVER_URL);
"#,
TEST_SERVER_URL
);
let result = engine.eval::<String>(&script);
match result {
Ok(info) => {
@@ -954,19 +1011,25 @@ fn test_rfs_get_system_info_with_server() {
#[test]
fn test_rfs_authenticate_with_server() {
if !is_server_running(TEST_SERVER_URL) {
println!("Skipping integration test - no RFS server running at {}", TEST_SERVER_URL);
println!(
"Skipping integration test - no RFS server running at {}",
TEST_SERVER_URL
);
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
// Test with dummy credentials (will likely fail, but tests the flow)
let script = format!(r#"
let script = format!(
r#"
rfs_create_client("{}", "{}", "{}", 30);
rfs_authenticate()
"#, TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD);
"#,
TEST_SERVER_URL, TEST_USERNAME, TEST_PASSWORD
);
let result = engine.eval::<bool>(&script);
match result {
Ok(success) => {
@@ -974,7 +1037,10 @@ fn test_rfs_authenticate_with_server() {
assert!(success);
}
Err(e) => {
println!("Expected authentication failure with dummy credentials: {}", e);
println!(
"Expected authentication failure with dummy credentials: {}",
e
);
// This is expected with dummy credentials
assert!(e.to_string().contains("Authentication failed"));
}
@@ -985,14 +1051,18 @@ fn test_rfs_authenticate_with_server() {
#[test]
fn test_complete_workflow_with_server() {
if !is_server_running(TEST_SERVER_URL) {
println!("Skipping integration test - no RFS server running at {}", TEST_SERVER_URL);
println!(
"Skipping integration test - no RFS server running at {}",
TEST_SERVER_URL
);
return;
}
let mut engine = Engine::new();
register_rfs_module(&mut engine).unwrap();
let script = format!(r#"
let script = format!(
r#"
// Create client
let client_created = rfs_create_client("{}", "", "", 60);
print("Client created: " + client_created);
@@ -1003,8 +1073,10 @@ fn test_complete_workflow_with_server() {
// Return success
client_created && info_result.len() > 0
"#, TEST_SERVER_URL);
"#,
TEST_SERVER_URL
);
let result = engine.eval::<bool>(&script);
match result {
Ok(success) => {