use crate::runtime::values::Value;
use crate::runtime::Runtime;
use std::collections::HashMap;
pub struct MockFunction {
pub name: String,
pub namespace: Option<String>,
pub return_value: Option<Value>,
pub side_effects: Vec<MockSideEffect>,
pub call_count: usize,
pub expected_calls: Option<usize>,
pub arguments_validator: Option<Box<dyn Fn(&[Value]) -> Result<(), String> + Send + Sync>>,
pub call_history: Vec<Vec<Value>>,
}
impl Clone for MockFunction {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
namespace: self.namespace.clone(),
return_value: self.return_value.clone(),
side_effects: self.side_effects.clone(),
call_count: self.call_count,
expected_calls: self.expected_calls,
arguments_validator: None, call_history: self.call_history.clone(),
}
}
}
impl MockFunction {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
namespace: None,
return_value: None,
side_effects: Vec::new(),
call_count: 0,
expected_calls: None,
arguments_validator: None,
call_history: Vec::new(),
}
}
pub fn with_namespace(mut self, namespace: &str) -> Self {
self.namespace = Some(namespace.to_string());
self
}
pub fn returns(mut self, value: Value) -> Self {
self.return_value = Some(value);
self
}
pub fn with_side_effect(mut self, side_effect: MockSideEffect) -> Self {
self.side_effects.push(side_effect);
self
}
pub fn expects_calls(mut self, count: usize) -> Self {
self.expected_calls = Some(count);
self
}
pub fn validates_arguments<F>(mut self, validator: F) -> Self
where
F: Fn(&[Value]) -> Result<(), String> + Send + Sync + 'static,
{
self.arguments_validator = Some(Box::new(validator));
self
}
pub fn last_call_args(&self) -> Option<&[Value]> {
self.call_history.last().map(|v| v.as_slice())
}
pub fn all_call_args(&self) -> &[Vec<Value>] {
&self.call_history
}
pub fn call(&mut self, args: &[Value]) -> Result<Value, String> {
if let Some(ref validator) = self.arguments_validator {
validator(args).map_err(|e| format!("Argument validation failed: {}", e))?;
}
self.call_count += 1;
self.call_history.push(args.to_vec());
for side_effect in &self.side_effects {
side_effect.execute(args)?;
}
Ok(self.return_value.clone().unwrap_or(Value::Null))
}
pub fn verify(&self) -> Result<(), String> {
if let Some(expected) = self.expected_calls {
if self.call_count != expected {
return Err(format!(
"Mock function '{}' was called {} times, expected {} times",
self.name, self.call_count, expected
));
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum MockSideEffect {
SetVariable(String, Value),
CallFunction(String, Vec<Value>),
LogMessage(String),
ThrowError(String),
Delay(std::time::Duration),
}
impl MockSideEffect {
pub fn execute(&self, _args: &[Value]) -> Result<(), String> {
match self {
MockSideEffect::SetVariable(var_name, _) => {
eprintln!("[MOCK WARNING] SetVariable side effect for '{}' requires runtime access. Use execute_with_runtime() or MockRuntime.", var_name);
Ok(())
}
MockSideEffect::CallFunction(func_name, _) => {
eprintln!("[MOCK WARNING] CallFunction side effect for '{}' requires runtime access. Use execute_with_runtime() or MockRuntime.", func_name);
Ok(())
}
MockSideEffect::LogMessage(message) => {
println!("[MOCK] {}", message);
Ok(())
}
MockSideEffect::ThrowError(error) => Err(error.clone()),
MockSideEffect::Delay(duration) => {
std::thread::sleep(*duration);
Ok(())
}
}
}
pub fn execute_with_runtime(
&self,
args: &[Value],
runtime: &mut Runtime,
) -> Result<(), String> {
match self {
MockSideEffect::SetVariable(var_name, value) => {
runtime.scope.set(var_name.clone(), value.clone());
Ok(())
}
MockSideEffect::CallFunction(func_name, call_args) => {
runtime
.call_function(func_name, call_args)
.map_err(|e| format!("Mock side effect CallFunction failed: {}", e))?;
Ok(())
}
_ => self.execute(args),
}
}
}
pub struct MockRegistry {
pub mocks: HashMap<String, MockFunction>,
pub enabled: bool,
}
impl MockRegistry {
pub fn new() -> Self {
Self {
mocks: HashMap::new(),
enabled: true,
}
}
pub fn register(&mut self, mock: MockFunction) {
let key = self.get_mock_key(&mock.name, mock.namespace.as_deref());
self.mocks.insert(key, mock);
}
pub fn get_mock(&mut self, name: &str, namespace: Option<&str>) -> Option<&mut MockFunction> {
let key = self.get_mock_key(name, namespace);
self.mocks.get_mut(&key)
}
pub fn call_mock(
&mut self,
name: &str,
namespace: Option<&str>,
args: &[Value],
) -> Result<Value, String> {
if !self.enabled {
return Err("Mock registry is disabled".to_string());
}
if let Some(mock) = self.get_mock(name, namespace) {
mock.call(args)
} else {
Err(format!(
"No mock found for '{}{}'",
namespace.map(|ns| format!("{}::", ns)).unwrap_or_default(),
name
))
}
}
pub fn call_mock_with_runtime(
&mut self,
name: &str,
namespace: Option<&str>,
args: &[Value],
runtime: &mut Runtime,
) -> Result<Value, String> {
if !self.enabled {
return Err("Mock registry is disabled".to_string());
}
let key = self.get_mock_key(name, namespace);
if let Some(mock) = self.mocks.get_mut(&key) {
if let Some(ref validator) = mock.arguments_validator {
validator(args).map_err(|e| format!("Argument validation failed: {}", e))?;
}
mock.call_count += 1;
mock.call_history.push(args.to_vec());
for side_effect in &mock.side_effects {
side_effect.execute_with_runtime(args, runtime)?;
}
Ok(mock.return_value.clone().unwrap_or(Value::Null))
} else {
Err(format!(
"No mock found for '{}{}'",
namespace.map(|ns| format!("{}::", ns)).unwrap_or_default(),
name
))
}
}
pub fn verify_all(&self) -> Result<(), String> {
for (key, mock) in &self.mocks {
mock.verify()
.map_err(|e| format!("Mock '{}': {}", key, e))?;
}
Ok(())
}
pub fn clear(&mut self) {
self.mocks.clear();
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn get_mock_key(&self, name: &str, namespace: Option<&str>) -> String {
match namespace {
Some(ns) => format!("{}::{}", ns, name),
None => name.to_string(),
}
}
pub fn has_mock(&self, name: &str, namespace: Option<&str>) -> bool {
let key = self.get_mock_key(name, namespace);
self.mocks.contains_key(&key)
}
pub fn get_call_count(&self, name: &str, namespace: Option<&str>) -> Option<usize> {
let key = self.get_mock_key(name, namespace);
self.mocks.get(&key).map(|m| m.call_count)
}
pub fn reset_call_counts(&mut self) {
for mock in self.mocks.values_mut() {
mock.call_count = 0;
mock.call_history.clear();
}
}
}
pub struct MockBuilder {
name: String,
namespace: Option<String>,
return_value: Option<Value>,
side_effects: Vec<MockSideEffect>,
expected_calls: Option<usize>,
arguments_validator: Option<Box<dyn Fn(&[Value]) -> Result<(), String> + Send + Sync>>,
}
impl MockBuilder {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
namespace: None,
return_value: None,
side_effects: Vec::new(),
expected_calls: None,
arguments_validator: None,
}
}
pub fn in_namespace(mut self, namespace: &str) -> Self {
self.namespace = Some(namespace.to_string());
self
}
pub fn returns(mut self, value: Value) -> Self {
self.return_value = Some(value);
self
}
pub fn logs(mut self, message: &str) -> Self {
self.side_effects
.push(MockSideEffect::LogMessage(message.to_string()));
self
}
pub fn throws(mut self, error: &str) -> Self {
self.side_effects
.push(MockSideEffect::ThrowError(error.to_string()));
self
}
pub fn delays(mut self, duration: std::time::Duration) -> Self {
self.side_effects.push(MockSideEffect::Delay(duration));
self
}
pub fn expects_calls(mut self, count: usize) -> Self {
self.expected_calls = Some(count);
self
}
pub fn validates_args<F>(mut self, validator: F) -> Self
where
F: Fn(&[Value]) -> Result<(), String> + Send + Sync + 'static,
{
self.arguments_validator = Some(Box::new(validator));
self
}
pub fn build(self) -> MockFunction {
MockFunction {
name: self.name,
namespace: self.namespace,
return_value: self.return_value,
side_effects: self.side_effects,
call_count: 0,
expected_calls: self.expected_calls,
arguments_validator: self.arguments_validator,
call_history: Vec::new(),
}
}
}
pub struct MockRuntime {
pub runtime: Runtime,
pub mock_registry: MockRegistry,
}
impl MockRuntime {
pub fn new() -> Self {
Self {
runtime: Runtime::new(),
mock_registry: MockRegistry::new(),
}
}
pub fn with_mock(mut self, mock: MockFunction) -> Self {
self.mock_registry.register(mock);
self
}
pub fn execute_with_mocks(&mut self, source_code: &str) -> Result<Value, String> {
use crate::lexer::Lexer;
use crate::parser::Parser;
self.mock_registry.enable();
let registry = std::mem::take(&mut self.mock_registry);
self.runtime.set_mock_registry(registry);
let tokens = Lexer::new(source_code)
.tokenize()
.map_err(|e| format!("Lexer error: {}", e))?;
let mut parser = Parser::new(tokens);
let program = parser.parse().map_err(|e| format!("Parser error: {}", e))?;
let result = self
.runtime
.execute_program(program, None)
.map_err(|e| format!("Runtime error: {}", e));
if let Some(rt_registry) = self.runtime.take_mock_registry() {
self.mock_registry = rt_registry;
}
match result {
Ok(_) => Ok(self.runtime.stack.last().cloned().unwrap_or(Value::Null)),
Err(e) => Err(e),
}
}
pub fn verify_mocks(&self) -> Result<(), String> {
self.mock_registry.verify_all()
}
}
pub mod mock_helpers {
use super::*;
pub fn mock_chain_mint() -> MockBuilder {
MockBuilder::new("mint")
.in_namespace("chain")
.returns(Value::Int(12345))
.logs("Mock chain::mint called")
}
pub fn mock_oracle_fetch() -> MockBuilder {
MockBuilder::new("fetch")
.in_namespace("oracle")
.returns(Value::String("mock_price_data".to_string()))
.logs("Mock oracle::fetch called")
}
pub fn mock_service_call() -> MockBuilder {
MockBuilder::new("call")
.in_namespace("service")
.returns(Value::String("mock_service_response".to_string()))
.logs("Mock service::call called")
}
pub fn mock_auth_session() -> MockBuilder {
MockBuilder::new("session")
.in_namespace("auth")
.returns(Value::String("mock_session_id".to_string()))
.logs("Mock auth::session called")
}
pub fn mock_crypto_hash() -> MockBuilder {
MockBuilder::new("hash")
.in_namespace("crypto")
.returns(Value::String("mock_hash_value".to_string()))
.logs("Mock crypto::hash called")
}
}
pub trait MockTestUtils {
fn setup_mocks(&mut self) -> &mut MockRegistry;
fn teardown_mocks(&mut self);
fn assert_mock_called(&self, name: &str, namespace: Option<&str>, expected_calls: usize);
fn assert_mock_not_called(&self, name: &str, namespace: Option<&str>);
}
impl MockTestUtils for MockRuntime {
fn setup_mocks(&mut self) -> &mut MockRegistry {
self.mock_registry.enable();
&mut self.mock_registry
}
fn teardown_mocks(&mut self) {
self.mock_registry.clear();
self.mock_registry.disable();
}
fn assert_mock_called(&self, name: &str, namespace: Option<&str>, expected_calls: usize) {
let key = self.mock_registry.get_mock_key(name, namespace);
if let Some(mock) = self.mock_registry.mocks.get(&key) {
assert_eq!(
mock.call_count, expected_calls,
"Mock '{}' was called {} times, expected {}",
key, mock.call_count, expected_calls
);
} else {
panic!("Mock '{}' not found", key);
}
}
fn assert_mock_not_called(&self, name: &str, namespace: Option<&str>) {
let key = self.mock_registry.get_mock_key(name, namespace);
if let Some(mock) = self.mock_registry.mocks.get(&key) {
assert_eq!(
mock.call_count, 0,
"Mock '{}' was called {} times, expected 0",
key, mock.call_count
);
}
}
}
impl Default for MockRegistry {
fn default() -> Self {
Self::new()
}
}
impl Default for MockRuntime {
fn default() -> Self {
Self::new()
}
}