Add Rhai scripting support for SAL #5
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -21,3 +21,4 @@ Cargo.lock | |||||||
| /target | /target | ||||||
| /rhai_test_download | /rhai_test_download | ||||||
| /rhai_test_fs | /rhai_test_fs | ||||||
|  | run_rhai_tests.log | ||||||
| @@ -33,6 +33,7 @@ SAL includes test scripts for verifying the functionality of its Rhai integratio | |||||||
| - [OS Module Tests](os_module_tests.md): Tests for file system, download, and package management operations | - [OS Module Tests](os_module_tests.md): Tests for file system, download, and package management operations | ||||||
| - [Git Module Tests](git_module_tests.md): Tests for Git repository management and operations | - [Git Module Tests](git_module_tests.md): Tests for Git repository management and operations | ||||||
| - [Process Module Tests](process_module_tests.md): Tests for command execution and process management | - [Process Module Tests](process_module_tests.md): Tests for command execution and process management | ||||||
|  | - [Redis Client Module Tests](redisclient_module_tests.md): Tests for Redis connection and operations | ||||||
| - [Running Tests](running_tests.md): Instructions for running all Rhai tests | - [Running Tests](running_tests.md): Instructions for running all Rhai tests | ||||||
| - [CI Workflow](ci_workflow.md): Continuous integration workflow for Rhai tests | - [CI Workflow](ci_workflow.md): Continuous integration workflow for Rhai tests | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										97
									
								
								docs/rhai/redisclient_module_tests.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								docs/rhai/redisclient_module_tests.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | # Redis Client Module Tests | ||||||
|  |  | ||||||
|  | This document describes the test scripts for the Redis client module in the SAL library. These tests verify the functionality of the Redis client module's connection management and Redis operations. | ||||||
|  |  | ||||||
|  | ## Test Structure | ||||||
|  |  | ||||||
|  | The tests are organized into two main scripts: | ||||||
|  |  | ||||||
|  | 1. **Redis Connection** (`01_redis_connection.rhai`): Tests basic Redis connection and simple operations like PING, SET, GET, and DEL. | ||||||
|  | 2. **Redis Operations** (`02_redis_operations.rhai`): Tests more advanced Redis operations like hash operations (HSET, HGET, HGETALL, HDEL) and list operations (RPUSH, LLEN, LRANGE). | ||||||
|  |  | ||||||
|  | Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues. | ||||||
|  |  | ||||||
|  | ## Running the Tests | ||||||
|  |  | ||||||
|  | To run all tests, execute the following command from the project root: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | herodo --path src/rhai_tests/redisclient/run_all_tests.rhai | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | To run individual test scripts: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | herodo --path src/rhai_tests/redisclient/01_redis_connection.rhai | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Test Details | ||||||
|  |  | ||||||
|  | ### Redis Connection Test | ||||||
|  |  | ||||||
|  | The Redis connection test (`01_redis_connection.rhai`) verifies the following functions: | ||||||
|  |  | ||||||
|  | - `redis_ping`: Checking if the Redis server is available | ||||||
|  | - `redis_set`: Setting a key-value pair | ||||||
|  | - `redis_get`: Getting a value by key | ||||||
|  | - `redis_del`: Deleting a key | ||||||
|  |  | ||||||
|  | The test creates a temporary key, performs operations on it, and then cleans up after itself. | ||||||
|  |  | ||||||
|  | ### Redis Operations Test | ||||||
|  |  | ||||||
|  | The Redis operations test (`02_redis_operations.rhai`) verifies the following functions: | ||||||
|  |  | ||||||
|  | - Hash operations: | ||||||
|  |   - `redis_hset`: Setting a field in a hash | ||||||
|  |   - `redis_hget`: Getting a field from a hash | ||||||
|  |   - `redis_hgetall`: Getting all fields and values from a hash | ||||||
|  |   - `redis_hdel`: Deleting a field from a hash | ||||||
|  |  | ||||||
|  | - List operations: | ||||||
|  |   - `redis_rpush`: Adding elements to a list | ||||||
|  |   - `redis_llen`: Getting the length of a list | ||||||
|  |   - `redis_lrange`: Getting a range of elements from a list | ||||||
|  |  | ||||||
|  | The test creates temporary keys with a unique prefix, performs operations on them, and then cleans up after itself. | ||||||
|  |  | ||||||
|  | ## Test Runner | ||||||
|  |  | ||||||
|  | The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It: | ||||||
|  |  | ||||||
|  | 1. Checks if Redis is available before running tests | ||||||
|  | 2. Skips tests if Redis is not available | ||||||
|  | 3. Contains simplified versions of each test | ||||||
|  | 4. Runs each test in a try/catch block to handle errors | ||||||
|  | 5. Catches and reports any errors | ||||||
|  | 6. Provides a summary of passed, failed, and skipped tests | ||||||
|  |  | ||||||
|  | ## Redis Server Requirements | ||||||
|  |  | ||||||
|  | These tests require a Redis server to be running and accessible. The tests will attempt to connect to Redis using the following strategy: | ||||||
|  |  | ||||||
|  | 1. First, try to connect via Unix socket at `$HOME/hero/var/myredis.sock` | ||||||
|  | 2. If that fails, try to connect via TCP to `127.0.0.1` on the default Redis port (6379) | ||||||
|  |  | ||||||
|  | If no Redis server is available, the tests will be skipped rather than failing. | ||||||
|  |  | ||||||
|  | ## Adding New Tests | ||||||
|  |  | ||||||
|  | To add a new test: | ||||||
|  |  | ||||||
|  | 1. Create a new Rhai script in the `src/rhai_tests/redisclient` directory | ||||||
|  | 2. Add a new test section to the `run_all_tests.rhai` script | ||||||
|  | 3. Update this documentation to include information about the new test | ||||||
|  |  | ||||||
|  | ## Best Practices for Writing Tests | ||||||
|  |  | ||||||
|  | When writing tests for the Redis client module: | ||||||
|  |  | ||||||
|  | 1. Always check if Redis is available before running tests | ||||||
|  | 2. Use a unique prefix for test keys to avoid conflicts | ||||||
|  | 3. Clean up any keys created during testing | ||||||
|  | 4. Use assertions to verify expected behavior | ||||||
|  | 5. Print clear messages about what's being tested | ||||||
|  | 6. Handle errors gracefully | ||||||
|  | 7. Make tests independent of each other | ||||||
|  | 8. Keep tests focused on specific functionality | ||||||
| @@ -1,131 +0,0 @@ | |||||||
| [0;34m=======================================[0m |  | ||||||
| [0;34m      Running All Rhai Tests          [0m |  | ||||||
| [0;34m=======================================[0m |  | ||||||
|  |  | ||||||
| [0;33mRunning tests for module: git[0m |  | ||||||
| [0;33m-------------------------------------[0m |  | ||||||
| Found 1 Rhai script to execute: |  | ||||||
|  |  | ||||||
| Executing: src/rhai_tests/git/run_all_tests.rhai |  | ||||||
| === Running Git Module Tests === |  | ||||||
|  |  | ||||||
| --- Running Basic Git Operations Tests --- |  | ||||||
| Created test directory: rhai_test_git |  | ||||||
| Testing GitTree constructor... |  | ||||||
| ✓ GitTree created successfully |  | ||||||
| Testing GitTree.list() with empty directory... |  | ||||||
| ✓ GitTree.list(): Found 0 repositories (expected 0) |  | ||||||
| Testing GitTree.find() with empty directory... |  | ||||||
| ✓ GitTree.find(): Found 0 repositories (expected 0) |  | ||||||
| Cleaning up... |  | ||||||
| ✓ Cleanup: Directory rhai_test_git removed |  | ||||||
| --- Basic Git Operations Tests completed successfully --- |  | ||||||
|  |  | ||||||
| --- Running Git Repository Operations Tests --- |  | ||||||
| Created test directory: rhai_test_git_ops |  | ||||||
| Creating GitTree... |  | ||||||
| ✓ GitTree created successfully |  | ||||||
| Cleaning up... |  | ||||||
| ✓ Cleanup: Directory rhai_test_git_ops removed |  | ||||||
| --- Git Repository Operations Tests completed successfully --- |  | ||||||
|  |  | ||||||
| === Test Summary === |  | ||||||
| Passed: 2 |  | ||||||
| Failed: 0 |  | ||||||
| Total: 2 |  | ||||||
|  |  | ||||||
| ✅ All tests passed! |  | ||||||
| Script executed successfully |  | ||||||
| Result: 0 |  | ||||||
|  |  | ||||||
| All scripts executed |  | ||||||
| [0;32m✓ Module git tests passed[0m |  | ||||||
|  |  | ||||||
| [0;33mRunning tests for module: os[0m |  | ||||||
| [0;33m-------------------------------------[0m |  | ||||||
| Found 1 Rhai script to execute: |  | ||||||
|  |  | ||||||
| Executing: src/rhai_tests/os/run_all_tests.rhai |  | ||||||
| === Running OS Module Tests === |  | ||||||
|  |  | ||||||
| --- Running File Operations Tests --- |  | ||||||
| Testing mkdir... |  | ||||||
| ✓ mkdir: Successfully created directory 'rhai_test_fs' |  | ||||||
| ✓ mkdir (nested): Successfully created directory 'rhai_test_fs/subdir' |  | ||||||
| ✓ file_write: Successfully wrote to file 'rhai_test_fs/test.txt' |  | ||||||
| ✓ file_read: Content matches |  | ||||||
| ✓ file_size: 48 bytes |  | ||||||
| ✓ delete: Directory cleaned up |  | ||||||
| --- File Operations Tests completed successfully --- |  | ||||||
|  |  | ||||||
| --- Running Download Operations Tests --- |  | ||||||
| Created test directory: rhai_test_download |  | ||||||
| ✓ which: curl found at /usr/bin/curl |  | ||||||
| ✓ cmd_ensure_exists: Command 'curl' exists |  | ||||||
| Downloading https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT... |  | ||||||
| Downloading https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT to rhai_test_download/license.txt |  | ||||||
| Download complete! File size: 1.04 KB |  | ||||||
| ✓ download_file: rhai_test_download/license.txt |  | ||||||
| ✓ Downloaded file content verified |  | ||||||
| ✓ Cleanup: Directory rhai_test_download removed |  | ||||||
| --- Download Operations Tests completed successfully --- |  | ||||||
|  |  | ||||||
| --- Running Package Operations Tests --- |  | ||||||
| Current platform: Ubuntu |  | ||||||
| ✓ package_set_debug: Debug mode enabled |  | ||||||
| --- Package Operations Tests completed successfully --- |  | ||||||
|  |  | ||||||
| === Test Summary === |  | ||||||
| Passed: 3 |  | ||||||
| Failed: 0 |  | ||||||
| Total: 3 |  | ||||||
|  |  | ||||||
| ✅ All tests passed! |  | ||||||
| Script executed successfully |  | ||||||
| Result: 0 |  | ||||||
|  |  | ||||||
| All scripts executed |  | ||||||
| [0;32m✓ Module os tests passed[0m |  | ||||||
|  |  | ||||||
| [0;33mRunning tests for module: process[0m |  | ||||||
| [0;33m-------------------------------------[0m |  | ||||||
| Found 1 Rhai script to execute: |  | ||||||
|  |  | ||||||
| Executing: src/rhai_tests/process/run_all_tests.rhai |  | ||||||
| === Running Process Module Tests === |  | ||||||
|  |  | ||||||
| --- Running Command Execution Tests --- |  | ||||||
| Testing run() with a simple command... |  | ||||||
| Hello, World! |  | ||||||
| ✓ run().execute(): Command executed successfully |  | ||||||
| Testing which() function... |  | ||||||
| ✓ which(): Found bash at /usr/bin/bash |  | ||||||
| --- Command Execution Tests completed successfully --- |  | ||||||
|  |  | ||||||
| --- Running Process Management Tests --- |  | ||||||
| Testing process_list() function... |  | ||||||
| ✓ process_list(): Found 344 processes |  | ||||||
| Testing process properties... |  | ||||||
| ✓ Process properties: PID=1, Name=systemd |  | ||||||
| --- Process Management Tests completed successfully --- |  | ||||||
|  |  | ||||||
| === Test Summary === |  | ||||||
| Passed: 2 |  | ||||||
| Failed: 0 |  | ||||||
| Total: 2 |  | ||||||
|  |  | ||||||
| ✅ All tests passed! |  | ||||||
| Script executed successfully |  | ||||||
| Result: 0 |  | ||||||
|  |  | ||||||
| All scripts executed |  | ||||||
| [0;32m✓ Module process tests passed[0m |  | ||||||
|  |  | ||||||
| [0;34m=======================================[0m |  | ||||||
| [0;34m            Test Summary              [0m |  | ||||||
| [0;34m=======================================[0m |  | ||||||
| Total modules tested: 3 |  | ||||||
| Passed: [0;32m3[0m |  | ||||||
| Failed: [0;31m0[0m |  | ||||||
|  |  | ||||||
| [0;32mAll tests passed![0m |  | ||||||
| @@ -9,6 +9,7 @@ mod git; | |||||||
| mod nerdctl; | mod nerdctl; | ||||||
| mod os; | mod os; | ||||||
| mod process; | mod process; | ||||||
|  | mod redisclient; | ||||||
| mod rfs; | mod rfs; | ||||||
| mod text; | mod text; | ||||||
|  |  | ||||||
| @@ -39,6 +40,9 @@ pub use os::{ | |||||||
|     rsync, |     rsync, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // Re-export Redis client module registration function | ||||||
|  | pub use redisclient::register_redisclient_module; | ||||||
|  |  | ||||||
| pub use process::{ | pub use process::{ | ||||||
|     kill, |     kill, | ||||||
|     process_get, |     process_get, | ||||||
| @@ -140,6 +144,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> { | |||||||
|     // Register RFS module functions |     // Register RFS module functions | ||||||
|     rfs::register(engine)?; |     rfs::register(engine)?; | ||||||
|  |  | ||||||
|  |     // Register Redis client module functions | ||||||
|  |     redisclient::register_redisclient_module(engine)?; | ||||||
|  |  | ||||||
|     // Future modules can be registered here |     // Future modules can be registered here | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
|   | |||||||
							
								
								
									
										323
									
								
								src/rhai/redisclient.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								src/rhai/redisclient.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | |||||||
|  | //! Rhai wrappers for Redis client module functions | ||||||
|  | //! | ||||||
|  | //! This module provides Rhai wrappers for the functions in the Redis client module. | ||||||
|  |  | ||||||
|  | use crate::redisclient; | ||||||
|  | use rhai::{Engine, EvalAltResult, Map}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  |  | ||||||
|  | /// Register Redis client module functions with the Rhai engine | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `engine` - The Rhai engine to register the functions with | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise | ||||||
|  | pub fn register_redisclient_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { | ||||||
|  |     // Register basic Redis operations | ||||||
|  |     engine.register_fn("redis_ping", redis_ping); | ||||||
|  |     engine.register_fn("redis_set", redis_set); | ||||||
|  |     engine.register_fn("redis_get", redis_get); | ||||||
|  |     engine.register_fn("redis_del", redis_del); | ||||||
|  |  | ||||||
|  |     // Register hash operations | ||||||
|  |     engine.register_fn("redis_hset", redis_hset); | ||||||
|  |     engine.register_fn("redis_hget", redis_hget); | ||||||
|  |     engine.register_fn("redis_hgetall", redis_hgetall); | ||||||
|  |     engine.register_fn("redis_hdel", redis_hdel); | ||||||
|  |  | ||||||
|  |     // Register list operations | ||||||
|  |     engine.register_fn("redis_rpush", redis_rpush); | ||||||
|  |     engine.register_fn("redis_lpush", redis_lpush); | ||||||
|  |     engine.register_fn("redis_llen", redis_llen); | ||||||
|  |     engine.register_fn("redis_lrange", redis_lrange); | ||||||
|  |  | ||||||
|  |     // Register other operations | ||||||
|  |     engine.register_fn("redis_reset", redis_reset); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Ping the Redis server | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<String, Box<EvalAltResult>>` - "PONG" if successful, error otherwise | ||||||
|  | pub fn redis_ping() -> Result<String, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("PING"); | ||||||
|  |     redisclient::execute(&mut cmd).map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Set a key-value pair in Redis | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The key to set | ||||||
|  | /// * `value` - The value to set | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise | ||||||
|  | pub fn redis_set(key: &str, value: &str) -> Result<bool, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("SET"); | ||||||
|  |     cmd.arg(key).arg(value); | ||||||
|  |     let result: redis::RedisResult<String> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(s) if s == "OK" => Ok(true), | ||||||
|  |         Ok(_) => Ok(false), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get a value from Redis by key | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The key to get | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<String, Box<EvalAltResult>>` - The value if found, empty string if not found, error otherwise | ||||||
|  | pub fn redis_get(key: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("GET"); | ||||||
|  |     cmd.arg(key); | ||||||
|  |     let result: redis::RedisResult<Option<String>> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(Some(value)) => Ok(value), | ||||||
|  |         Ok(None) => Ok(String::new()), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Delete a key from Redis | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The key to delete | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise | ||||||
|  | pub fn redis_del(key: &str) -> Result<bool, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("DEL"); | ||||||
|  |     cmd.arg(key); | ||||||
|  |     let result: redis::RedisResult<i64> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(n) => Ok(n > 0), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Set a field in a hash | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The hash key | ||||||
|  | /// * `field` - The field to set | ||||||
|  | /// * `value` - The value to set | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise | ||||||
|  | pub fn redis_hset(key: &str, field: &str, value: &str) -> Result<bool, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("HSET"); | ||||||
|  |     cmd.arg(key).arg(field).arg(value); | ||||||
|  |     let result: redis::RedisResult<i64> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(_) => Ok(true), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get a field from a hash | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The hash key | ||||||
|  | /// * `field` - The field to get | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<String, Box<EvalAltResult>>` - The value if found, empty string if not found, error otherwise | ||||||
|  | pub fn redis_hget(key: &str, field: &str) -> Result<String, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("HGET"); | ||||||
|  |     cmd.arg(key).arg(field); | ||||||
|  |     let result: redis::RedisResult<Option<String>> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(Some(value)) => Ok(value), | ||||||
|  |         Ok(None) => Ok(String::new()), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get all fields and values from a hash | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The hash key | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<Map, Box<EvalAltResult>>` - A map of field-value pairs, error otherwise | ||||||
|  | pub fn redis_hgetall(key: &str) -> Result<Map, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("HGETALL"); | ||||||
|  |     cmd.arg(key); | ||||||
|  |     let result: redis::RedisResult<HashMap<String, String>> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(hash_map) => { | ||||||
|  |             let mut map = Map::new(); | ||||||
|  |             for (k, v) in hash_map { | ||||||
|  |                 map.insert(k.into(), v.into()); | ||||||
|  |             } | ||||||
|  |             Ok(map) | ||||||
|  |         } | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Delete a field from a hash | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The hash key | ||||||
|  | /// * `field` - The field to delete | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise | ||||||
|  | pub fn redis_hdel(key: &str, field: &str) -> Result<bool, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("HDEL"); | ||||||
|  |     cmd.arg(key).arg(field); | ||||||
|  |     let result: redis::RedisResult<i64> = redisclient::execute(&mut cmd); | ||||||
|  |     match result { | ||||||
|  |         Ok(n) => Ok(n > 0), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Push an element to the end of a list | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The list key | ||||||
|  | /// * `value` - The value to push | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<i64, Box<EvalAltResult>>` - The new length of the list, error otherwise | ||||||
|  | pub fn redis_rpush(key: &str, value: &str) -> Result<i64, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("RPUSH"); | ||||||
|  |     cmd.arg(key).arg(value); | ||||||
|  |     redisclient::execute(&mut cmd).map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Push an element to the beginning of a list | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The list key | ||||||
|  | /// * `value` - The value to push | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<i64, Box<EvalAltResult>>` - The new length of the list, error otherwise | ||||||
|  | pub fn redis_lpush(key: &str, value: &str) -> Result<i64, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("LPUSH"); | ||||||
|  |     cmd.arg(key).arg(value); | ||||||
|  |     redisclient::execute(&mut cmd).map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get the length of a list | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The list key | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<i64, Box<EvalAltResult>>` - The length of the list, error otherwise | ||||||
|  | pub fn redis_llen(key: &str) -> Result<i64, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("LLEN"); | ||||||
|  |     cmd.arg(key); | ||||||
|  |     redisclient::execute(&mut cmd).map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get a range of elements from a list | ||||||
|  | /// | ||||||
|  | /// # Arguments | ||||||
|  | /// | ||||||
|  | /// * `key` - The list key | ||||||
|  | /// * `start` - The start index | ||||||
|  | /// * `stop` - The stop index | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<Vec<String>, Box<EvalAltResult>>` - The elements in the range, error otherwise | ||||||
|  | pub fn redis_lrange(key: &str, start: i64, stop: i64) -> Result<Vec<String>, Box<EvalAltResult>> { | ||||||
|  |     let mut cmd = redis::cmd("LRANGE"); | ||||||
|  |     cmd.arg(key).arg(start).arg(stop); | ||||||
|  |     redisclient::execute(&mut cmd).map_err(|e| { | ||||||
|  |         Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         )) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Reset the Redis client connection | ||||||
|  | /// | ||||||
|  | /// # Returns | ||||||
|  | /// | ||||||
|  | /// * `Result<bool, Box<EvalAltResult>>` - true if successful, error otherwise | ||||||
|  | pub fn redis_reset() -> Result<bool, Box<EvalAltResult>> { | ||||||
|  |     match redisclient::reset() { | ||||||
|  |         Ok(_) => Ok(true), | ||||||
|  |         Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( | ||||||
|  |             format!("Redis error: {}", e).into(), | ||||||
|  |             rhai::Position::NONE, | ||||||
|  |         ))), | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								src/rhai_tests/redisclient/01_redis_connection.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/rhai_tests/redisclient/01_redis_connection.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | // 01_redis_connection.rhai | ||||||
|  | // Tests for Redis client connection and basic operations | ||||||
|  |  | ||||||
|  | // Custom assert function | ||||||
|  | fn assert_true(condition, message) { | ||||||
|  |     if !condition { | ||||||
|  |         print(`ASSERTION FAILED: ${message}`); | ||||||
|  |         throw message; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Helper function to check if Redis is available | ||||||
|  | fn is_redis_available() { | ||||||
|  |     try { | ||||||
|  |         // Try to execute a simple PING command | ||||||
|  |         let ping_result = redis_ping(); | ||||||
|  |         return ping_result == "PONG"; | ||||||
|  |     } catch(err) { | ||||||
|  |         print(`Redis connection error: ${err}`); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print("=== Testing Redis Client Connection ==="); | ||||||
|  |  | ||||||
|  | // Check if Redis is available | ||||||
|  | let redis_available = is_redis_available(); | ||||||
|  | if !redis_available { | ||||||
|  |     print("Redis server is not available. Skipping Redis tests."); | ||||||
|  |     // Exit gracefully without error | ||||||
|  |     return; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print("✓ Redis server is available"); | ||||||
|  |  | ||||||
|  | // Test redis_ping function | ||||||
|  | print("Testing redis_ping()..."); | ||||||
|  | let ping_result = redis_ping(); | ||||||
|  | assert_true(ping_result == "PONG", "PING should return PONG"); | ||||||
|  | print(`✓ redis_ping(): Returned ${ping_result}`); | ||||||
|  |  | ||||||
|  | // Test redis_set and redis_get functions | ||||||
|  | print("Testing redis_set() and redis_get()..."); | ||||||
|  | let test_key = "rhai_test_key"; | ||||||
|  | let test_value = "Hello from Rhai test"; | ||||||
|  |  | ||||||
|  | // Set a value | ||||||
|  | let set_result = redis_set(test_key, test_value); | ||||||
|  | assert_true(set_result, "SET operation should succeed"); | ||||||
|  | print(`✓ redis_set(): Successfully set key ${test_key}`); | ||||||
|  |  | ||||||
|  | // Get the value back | ||||||
|  | let get_result = redis_get(test_key); | ||||||
|  | assert_true(get_result == test_value, "GET should return the value we set"); | ||||||
|  | print(`✓ redis_get(): Successfully retrieved value for key ${test_key}`); | ||||||
|  |  | ||||||
|  | // Test redis_del function | ||||||
|  | print("Testing redis_del()..."); | ||||||
|  | let del_result = redis_del(test_key); | ||||||
|  | assert_true(del_result, "DEL operation should succeed"); | ||||||
|  | print(`✓ redis_del(): Successfully deleted key ${test_key}`); | ||||||
|  |  | ||||||
|  | // Verify the key was deleted | ||||||
|  | let get_after_del = redis_get(test_key); | ||||||
|  | assert_true(get_after_del == "", "Key should not exist after deletion"); | ||||||
|  | print("✓ Key was successfully deleted"); | ||||||
|  |  | ||||||
|  | print("All Redis connection tests completed successfully!"); | ||||||
							
								
								
									
										109
									
								
								src/rhai_tests/redisclient/02_redis_operations.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/rhai_tests/redisclient/02_redis_operations.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | // 02_redis_operations.rhai | ||||||
|  | // Tests for advanced Redis operations | ||||||
|  |  | ||||||
|  | // Custom assert function | ||||||
|  | fn assert_true(condition, message) { | ||||||
|  |     if !condition { | ||||||
|  |         print(`ASSERTION FAILED: ${message}`); | ||||||
|  |         throw message; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Helper function to check if Redis is available | ||||||
|  | fn is_redis_available() { | ||||||
|  |     try { | ||||||
|  |         // Try to execute a simple PING command | ||||||
|  |         let ping_result = redis_ping(); | ||||||
|  |         return ping_result == "PONG"; | ||||||
|  |     } catch(err) { | ||||||
|  |         print(`Redis connection error: ${err}`); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print("=== Testing Advanced Redis Operations ==="); | ||||||
|  |  | ||||||
|  | // Check if Redis is available | ||||||
|  | let redis_available = is_redis_available(); | ||||||
|  | if !redis_available { | ||||||
|  |     print("Redis server is not available. Skipping Redis tests."); | ||||||
|  |     // Exit gracefully without error | ||||||
|  |     return; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print("✓ Redis server is available"); | ||||||
|  |  | ||||||
|  | // Test prefix for all keys to avoid conflicts | ||||||
|  | let prefix = "rhai_test_"; | ||||||
|  |  | ||||||
|  | // Test redis_hset and redis_hget functions | ||||||
|  | print("Testing redis_hset() and redis_hget()..."); | ||||||
|  | let hash_key = prefix + "hash"; | ||||||
|  | let field1 = "field1"; | ||||||
|  | let value1 = "value1"; | ||||||
|  | let field2 = "field2"; | ||||||
|  | let value2 = "value2"; | ||||||
|  |  | ||||||
|  | // Set hash fields | ||||||
|  | let hset_result1 = redis_hset(hash_key, field1, value1); | ||||||
|  | assert_true(hset_result1, "HSET operation should succeed for field1"); | ||||||
|  | let hset_result2 = redis_hset(hash_key, field2, value2); | ||||||
|  | assert_true(hset_result2, "HSET operation should succeed for field2"); | ||||||
|  | print(`✓ redis_hset(): Successfully set fields in hash ${hash_key}`); | ||||||
|  |  | ||||||
|  | // Get hash fields | ||||||
|  | let hget_result1 = redis_hget(hash_key, field1); | ||||||
|  | assert_true(hget_result1 == value1, "HGET should return the value we set for field1"); | ||||||
|  | let hget_result2 = redis_hget(hash_key, field2); | ||||||
|  | assert_true(hget_result2 == value2, "HGET should return the value we set for field2"); | ||||||
|  | print(`✓ redis_hget(): Successfully retrieved values from hash ${hash_key}`); | ||||||
|  |  | ||||||
|  | // Test redis_hgetall function | ||||||
|  | print("Testing redis_hgetall()..."); | ||||||
|  | let hgetall_result = redis_hgetall(hash_key); | ||||||
|  | assert_true(hgetall_result.len() == 2, "HGETALL should return 2 fields"); | ||||||
|  | assert_true(hgetall_result[field1] == value1, "HGETALL should include field1 with correct value"); | ||||||
|  | assert_true(hgetall_result[field2] == value2, "HGETALL should include field2 with correct value"); | ||||||
|  | print(`✓ redis_hgetall(): Successfully retrieved all fields from hash ${hash_key}`); | ||||||
|  |  | ||||||
|  | // Test redis_hdel function | ||||||
|  | print("Testing redis_hdel()..."); | ||||||
|  | let hdel_result = redis_hdel(hash_key, field1); | ||||||
|  | assert_true(hdel_result, "HDEL operation should succeed"); | ||||||
|  | print(`✓ redis_hdel(): Successfully deleted field from hash ${hash_key}`); | ||||||
|  |  | ||||||
|  | // Verify the field was deleted | ||||||
|  | let hget_after_del = redis_hget(hash_key, field1); | ||||||
|  | assert_true(hget_after_del == "", "Field should not exist after deletion"); | ||||||
|  | print("✓ Field was successfully deleted from hash"); | ||||||
|  |  | ||||||
|  | // Test redis_list operations | ||||||
|  | print("Testing redis list operations..."); | ||||||
|  | let list_key = prefix + "list"; | ||||||
|  |  | ||||||
|  | // Push items to list | ||||||
|  | let rpush_result = redis_rpush(list_key, "item1"); | ||||||
|  | assert_true(rpush_result > 0, "RPUSH operation should succeed"); | ||||||
|  | redis_rpush(list_key, "item2"); | ||||||
|  | redis_rpush(list_key, "item3"); | ||||||
|  | print(`✓ redis_rpush(): Successfully pushed items to list ${list_key}`); | ||||||
|  |  | ||||||
|  | // Get list length | ||||||
|  | let llen_result = redis_llen(list_key); | ||||||
|  | assert_true(llen_result == 3, "List should have 3 items"); | ||||||
|  | print(`✓ redis_llen(): List has ${llen_result} items`); | ||||||
|  |  | ||||||
|  | // Get list range | ||||||
|  | let lrange_result = redis_lrange(list_key, 0, -1); | ||||||
|  | assert_true(lrange_result.len() == 3, "LRANGE should return 3 items"); | ||||||
|  | assert_true(lrange_result[0] == "item1", "First item should be 'item1'"); | ||||||
|  | assert_true(lrange_result[2] == "item3", "Last item should be 'item3'"); | ||||||
|  | print(`✓ redis_lrange(): Successfully retrieved all items from list ${list_key}`); | ||||||
|  |  | ||||||
|  | // Clean up | ||||||
|  | print("Cleaning up..."); | ||||||
|  | redis_del(hash_key); | ||||||
|  | redis_del(list_key); | ||||||
|  | print("✓ Cleanup: All test keys removed"); | ||||||
|  |  | ||||||
|  | print("All Redis operations tests completed successfully!"); | ||||||
							
								
								
									
										121
									
								
								src/rhai_tests/redisclient/run_all_tests.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/rhai_tests/redisclient/run_all_tests.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | // run_all_tests.rhai | ||||||
|  | // Runs all Redis client module tests | ||||||
|  |  | ||||||
|  | print("=== Running Redis Client Module Tests ==="); | ||||||
|  |  | ||||||
|  | // Custom assert function | ||||||
|  | fn assert_true(condition, message) { | ||||||
|  |     if !condition { | ||||||
|  |         print(`ASSERTION FAILED: ${message}`); | ||||||
|  |         throw message; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Helper function to check if Redis is available | ||||||
|  | fn is_redis_available() { | ||||||
|  |     try { | ||||||
|  |         // Try to execute a simple PING command | ||||||
|  |         let ping_result = redis_ping(); | ||||||
|  |         return ping_result == "PONG"; | ||||||
|  |     } catch(err) { | ||||||
|  |         print(`Redis connection error: ${err}`); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Run each test directly | ||||||
|  | let passed = 0; | ||||||
|  | let failed = 0; | ||||||
|  | let skipped = 0; | ||||||
|  |  | ||||||
|  | // Check if Redis is available | ||||||
|  | let redis_available = is_redis_available(); | ||||||
|  | if !redis_available { | ||||||
|  |     print("Redis server is not available. Skipping all Redis tests."); | ||||||
|  |     skipped = 2; // Skip both tests | ||||||
|  | } else { | ||||||
|  |     // Test 1: Redis Connection | ||||||
|  |     print("\n--- Running Redis Connection Tests ---"); | ||||||
|  |     try { | ||||||
|  |         // Test redis_ping function | ||||||
|  |         print("Testing redis_ping()..."); | ||||||
|  |         let ping_result = redis_ping(); | ||||||
|  |         assert_true(ping_result == "PONG", "PING should return PONG"); | ||||||
|  |         print(`✓ redis_ping(): Returned ${ping_result}`); | ||||||
|  |  | ||||||
|  |         // Test redis_set and redis_get functions | ||||||
|  |         print("Testing redis_set() and redis_get()..."); | ||||||
|  |         let test_key = "rhai_test_key"; | ||||||
|  |         let test_value = "Hello from Rhai test"; | ||||||
|  |  | ||||||
|  |         // Set a value | ||||||
|  |         let set_result = redis_set(test_key, test_value); | ||||||
|  |         assert_true(set_result, "SET operation should succeed"); | ||||||
|  |         print(`✓ redis_set(): Successfully set key ${test_key}`); | ||||||
|  |  | ||||||
|  |         // Get the value back | ||||||
|  |         let get_result = redis_get(test_key); | ||||||
|  |         assert_true(get_result == test_value, "GET should return the value we set"); | ||||||
|  |         print(`✓ redis_get(): Successfully retrieved value for key ${test_key}`); | ||||||
|  |  | ||||||
|  |         // Clean up | ||||||
|  |         redis_del(test_key); | ||||||
|  |  | ||||||
|  |         print("--- Redis Connection Tests completed successfully ---"); | ||||||
|  |         passed += 1; | ||||||
|  |     } catch(err) { | ||||||
|  |         print(`!!! Error in Redis Connection Tests: ${err}`); | ||||||
|  |         failed += 1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Test 2: Redis Operations | ||||||
|  |     print("\n--- Running Redis Operations Tests ---"); | ||||||
|  |     try { | ||||||
|  |         // Test prefix for all keys to avoid conflicts | ||||||
|  |         let prefix = "rhai_test_"; | ||||||
|  |  | ||||||
|  |         // Test redis_hset and redis_hget functions | ||||||
|  |         print("Testing redis_hset() and redis_hget()..."); | ||||||
|  |         let hash_key = prefix + "hash"; | ||||||
|  |         let field = "field1"; | ||||||
|  |         let value = "value1"; | ||||||
|  |  | ||||||
|  |         // Set hash field | ||||||
|  |         let hset_result = redis_hset(hash_key, field, value); | ||||||
|  |         assert_true(hset_result, "HSET operation should succeed"); | ||||||
|  |         print(`✓ redis_hset(): Successfully set field in hash ${hash_key}`); | ||||||
|  |  | ||||||
|  |         // Get hash field | ||||||
|  |         let hget_result = redis_hget(hash_key, field); | ||||||
|  |         assert_true(hget_result == value, "HGET should return the value we set"); | ||||||
|  |         print(`✓ redis_hget(): Successfully retrieved value from hash ${hash_key}`); | ||||||
|  |  | ||||||
|  |         // Clean up | ||||||
|  |         redis_del(hash_key); | ||||||
|  |  | ||||||
|  |         print("--- Redis Operations Tests completed successfully ---"); | ||||||
|  |         passed += 1; | ||||||
|  |     } catch(err) { | ||||||
|  |         print(`!!! Error in Redis Operations Tests: ${err}`); | ||||||
|  |         failed += 1; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | print("\n=== Test Summary ==="); | ||||||
|  | print(`Passed: ${passed}`); | ||||||
|  | print(`Failed: ${failed}`); | ||||||
|  | print(`Skipped: ${skipped}`); | ||||||
|  | print(`Total: ${passed + failed + skipped}`); | ||||||
|  |  | ||||||
|  | if failed == 0 { | ||||||
|  |     if skipped > 0 { | ||||||
|  |         print("\n⚠️ All tests skipped or passed!"); | ||||||
|  |     } else { | ||||||
|  |         print("\n✅ All tests passed!"); | ||||||
|  |     } | ||||||
|  | } else { | ||||||
|  |     print("\n❌ Some tests failed!"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Return the number of failed tests (0 means success) | ||||||
|  | failed; | ||||||
		Reference in New Issue
	
	Block a user