Update test structure and cleanup deprecated tests
This commit is contained in:
parent
56334dd7b1
commit
b38574c588
146 changed files with 332 additions and 9605 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use super::{BotResponse, ConversationState, ResponseContentType};
|
use super::{BotResponse, ConversationState, ResponseContentType};
|
||||||
use crate::fixtures::{Bot, Channel, Customer, Session};
|
use crate::fixtures::{Bot, Channel, Customer, Session};
|
||||||
use crate::harness::TestContext;
|
use crate::harness::TestContext;
|
||||||
|
|
@ -137,7 +136,7 @@ impl BotRunner {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_script(&mut self, name: &str, content: &str) -> &mut Self {
|
pub fn load_script(&self, name: &str, content: &str) -> &Self {
|
||||||
self.script_cache
|
self.script_cache
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -145,9 +144,9 @@ impl BotRunner {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_script_file(&mut self, name: &str, path: &PathBuf) -> Result<&mut Self> {
|
pub fn load_script_file(&self, name: &str, path: &PathBuf) -> Result<&Self> {
|
||||||
let content = std::fs::read_to_string(path)
|
let content = std::fs::read_to_string(path)
|
||||||
.with_context(|| format!("Failed to read script file: {path:?}"))?;
|
.with_context(|| format!("Failed to read script file: {}", path.display()))?;
|
||||||
self.script_cache
|
self.script_cache
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -155,7 +154,7 @@ impl BotRunner {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_session(&mut self, customer: Customer) -> Result<Uuid> {
|
pub fn start_session(&self, customer: Customer) -> Result<Uuid> {
|
||||||
let session_id = Uuid::new_v4();
|
let session_id = Uuid::new_v4();
|
||||||
let bot_id = self.bot.as_ref().map_or_else(Uuid::new_v4, |b| b.id);
|
let bot_id = self.bot.as_ref().map_or_else(Uuid::new_v4, |b| b.id);
|
||||||
|
|
||||||
|
|
@ -182,13 +181,13 @@ impl BotRunner {
|
||||||
Ok(session_id)
|
Ok(session_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_session(&mut self, session_id: Uuid) -> Result<()> {
|
pub fn end_session(&self, session_id: Uuid) -> Result<()> {
|
||||||
self.sessions.lock().unwrap().remove(&session_id);
|
self.sessions.lock().unwrap().remove(&session_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_message(
|
pub async fn process_message(
|
||||||
&mut self,
|
&self,
|
||||||
session_id: Uuid,
|
session_id: Uuid,
|
||||||
message: &str,
|
message: &str,
|
||||||
) -> Result<ExecutionResult> {
|
) -> Result<ExecutionResult> {
|
||||||
|
|
@ -205,18 +204,15 @@ impl BotRunner {
|
||||||
sessions.get(&session_id).cloned()
|
sessions.get(&session_id).cloned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = match state {
|
let Some(state) = state else {
|
||||||
Some(s) => s,
|
return Ok(ExecutionResult {
|
||||||
None => {
|
session_id,
|
||||||
return Ok(ExecutionResult {
|
response: None,
|
||||||
session_id,
|
state: ConversationState::Error,
|
||||||
response: None,
|
execution_time: start.elapsed(),
|
||||||
state: ConversationState::Error,
|
logs,
|
||||||
execution_time: start.elapsed(),
|
error: Some("Session not found".to_string()),
|
||||||
logs,
|
});
|
||||||
error: Some("Session not found".to_string()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.config.capture_logs {
|
if self.config.capture_logs {
|
||||||
|
|
@ -308,17 +304,18 @@ impl BotRunner {
|
||||||
let response_content = if script_content.is_empty() {
|
let response_content = if script_content.is_empty() {
|
||||||
format!("Received: {message}")
|
format!("Received: {message}")
|
||||||
} else {
|
} else {
|
||||||
self.evaluate_basic_script(&script_content, message, &state.context)
|
Self::evaluate_basic_script(&script_content, message, &state.context)
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| format!("Error: {e}"))
|
.unwrap_or_else(|e| format!("Error: {e}"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let latency = start.elapsed().as_millis() as u64;
|
let latency = start.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
let mut metrics = self.metrics.lock().unwrap();
|
{
|
||||||
metrics.total_requests += 1;
|
let mut metrics = self.metrics.lock().unwrap();
|
||||||
metrics.successful_requests += 1;
|
metrics.total_requests += 1;
|
||||||
metrics.total_latency_ms += latency;
|
metrics.successful_requests += 1;
|
||||||
|
metrics.total_latency_ms += latency;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(BotResponse {
|
Ok(BotResponse {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
|
|
@ -338,8 +335,7 @@ impl BotRunner {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn evaluate_basic_script(
|
fn evaluate_basic_script(
|
||||||
&self,
|
|
||||||
script: &str,
|
script: &str,
|
||||||
input: &str,
|
input: &str,
|
||||||
context: &HashMap<String, serde_json::Value>,
|
context: &HashMap<String, serde_json::Value>,
|
||||||
|
|
@ -360,7 +356,7 @@ impl BotRunner {
|
||||||
|
|
||||||
if line.to_uppercase().starts_with("TALK") {
|
if line.to_uppercase().starts_with("TALK") {
|
||||||
let content = line[4..].trim().trim_matches('"');
|
let content = line[4..].trim().trim_matches('"');
|
||||||
let expanded = self.expand_variables(content, &variables);
|
let expanded = Self::expand_variables(content, &variables);
|
||||||
if !output.is_empty() {
|
if !output.is_empty() {
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
@ -372,7 +368,7 @@ impl BotRunner {
|
||||||
if parts.len() == 2 {
|
if parts.len() == 2 {
|
||||||
let var_name = parts[0].trim().to_uppercase();
|
let var_name = parts[0].trim().to_uppercase();
|
||||||
let var_value = parts[1].trim().trim_matches('"').to_string();
|
let var_value = parts[1].trim().trim_matches('"').to_string();
|
||||||
let expanded = self.expand_variables(&var_value, &variables);
|
let expanded = Self::expand_variables(&var_value, &variables);
|
||||||
variables.insert(var_name, expanded);
|
variables.insert(var_name, expanded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -385,7 +381,7 @@ impl BotRunner {
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_variables(&self, text: &str, variables: &HashMap<String, String>) -> String {
|
fn expand_variables(text: &str, variables: &HashMap<String, String>) -> String {
|
||||||
let mut result = text.to_string();
|
let mut result = text.to_string();
|
||||||
for (key, value) in variables {
|
for (key, value) in variables {
|
||||||
result = result.replace(&format!("{{{key}}}"), value);
|
result = result.replace(&format!("{{{key}}}"), value);
|
||||||
|
|
@ -395,11 +391,7 @@ impl BotRunner {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_script(
|
pub fn execute_script(&self, script_name: &str, input: &str) -> Result<ExecutionResult> {
|
||||||
&mut self,
|
|
||||||
script_name: &str,
|
|
||||||
input: &str,
|
|
||||||
) -> Result<ExecutionResult> {
|
|
||||||
let session_id = Uuid::new_v4();
|
let session_id = Uuid::new_v4();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut logs = Vec::new();
|
let mut logs = Vec::new();
|
||||||
|
|
@ -409,18 +401,15 @@ impl BotRunner {
|
||||||
cache.get(script_name).cloned()
|
cache.get(script_name).cloned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let script = match script {
|
let Some(script) = script else {
|
||||||
Some(s) => s,
|
return Ok(ExecutionResult {
|
||||||
None => {
|
session_id,
|
||||||
return Ok(ExecutionResult {
|
response: None,
|
||||||
session_id,
|
state: ConversationState::Error,
|
||||||
response: None,
|
execution_time: start.elapsed(),
|
||||||
state: ConversationState::Error,
|
logs,
|
||||||
execution_time: start.elapsed(),
|
error: Some(format!("Script '{script_name}' not found")),
|
||||||
logs,
|
});
|
||||||
error: Some(format!("Script '{script_name}' not found")),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.config.capture_logs {
|
if self.config.capture_logs {
|
||||||
|
|
@ -437,7 +426,7 @@ impl BotRunner {
|
||||||
metrics.script_executions += 1;
|
metrics.script_executions += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = self.execute_script_internal(&script, input).await;
|
let result = Self::execute_script_internal(&script, input);
|
||||||
|
|
||||||
let execution_time = start.elapsed();
|
let execution_time = start.elapsed();
|
||||||
|
|
||||||
|
|
@ -467,16 +456,16 @@ impl BotRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_script_internal(&self, script: &str, input: &str) -> Result<String> {
|
fn execute_script_internal(script: &str, input: &str) -> Result<String> {
|
||||||
let context = HashMap::new();
|
let context = HashMap::new();
|
||||||
self.evaluate_basic_script(script, input, &context).await
|
Self::evaluate_basic_script(script, input, &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metrics(&self) -> RunnerMetrics {
|
pub fn metrics(&self) -> RunnerMetrics {
|
||||||
self.metrics.lock().unwrap().clone()
|
self.metrics.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_metrics(&mut self) {
|
pub fn reset_metrics(&self) {
|
||||||
*self.metrics.lock().unwrap() = RunnerMetrics::default();
|
*self.metrics.lock().unwrap() = RunnerMetrics::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,14 +475,16 @@ impl BotRunner {
|
||||||
|
|
||||||
pub fn get_session_info(&self, session_id: Uuid) -> Option<SessionInfo> {
|
pub fn get_session_info(&self, session_id: Uuid) -> Option<SessionInfo> {
|
||||||
let sessions = self.sessions.lock().unwrap();
|
let sessions = self.sessions.lock().unwrap();
|
||||||
sessions.get(&session_id).map(|s| SessionInfo {
|
let info = sessions.get(&session_id).map(|s| SessionInfo {
|
||||||
session_id: s.session.id,
|
session_id: s.session.id,
|
||||||
customer_id: s.customer.id,
|
customer_id: s.customer.id,
|
||||||
channel: s.channel,
|
channel: s.channel,
|
||||||
message_count: s.message_count,
|
message_count: s.message_count,
|
||||||
state: s.conversation_state,
|
state: s.conversation_state,
|
||||||
duration: s.started_at.elapsed(),
|
duration: s.started_at.elapsed(),
|
||||||
})
|
});
|
||||||
|
drop(sessions);
|
||||||
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_env(&mut self, key: &str, value: &str) -> &mut Self {
|
pub fn set_env(&mut self, key: &str, value: &str) -> &mut Self {
|
||||||
|
|
@ -553,27 +544,31 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_runner_metrics_avg_latency() {
|
fn test_runner_metrics_avg_latency() {
|
||||||
let mut metrics = RunnerMetrics::default();
|
let metrics = RunnerMetrics {
|
||||||
metrics.total_requests = 10;
|
total_requests: 10,
|
||||||
metrics.total_latency_ms = 1000;
|
total_latency_ms: 1000,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(metrics.avg_latency_ms(), 100);
|
assert_eq!(metrics.avg_latency_ms(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_runner_metrics_success_rate() {
|
fn test_runner_metrics_success_rate() {
|
||||||
let mut metrics = RunnerMetrics::default();
|
let metrics = RunnerMetrics {
|
||||||
metrics.total_requests = 100;
|
total_requests: 100,
|
||||||
metrics.successful_requests = 95;
|
successful_requests: 95,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(metrics.success_rate(), 95.0);
|
assert!((metrics.success_rate() - 95.0).abs() < f64::EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_runner_metrics_zero_requests() {
|
fn test_runner_metrics_zero_requests() {
|
||||||
let metrics = RunnerMetrics::default();
|
let metrics = RunnerMetrics::default();
|
||||||
assert_eq!(metrics.avg_latency_ms(), 0);
|
assert_eq!(metrics.avg_latency_ms(), 0);
|
||||||
assert_eq!(metrics.success_rate(), 0.0);
|
assert!(metrics.success_rate().abs() < f64::EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -584,16 +579,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_script() {
|
fn test_load_script() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
runner.load_script("test", "TALK \"Hello\"");
|
runner.load_script("test", "TALK \"Hello\"");
|
||||||
|
|
||||||
let cache = runner.script_cache.lock().unwrap();
|
let cache = runner.script_cache.lock().unwrap();
|
||||||
assert!(cache.contains_key("test"));
|
assert!(cache.contains_key("test"));
|
||||||
|
drop(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start_session() {
|
fn test_start_session() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
let customer = Customer::default();
|
let customer = Customer::default();
|
||||||
|
|
||||||
let session_id = runner.start_session(customer).unwrap();
|
let session_id = runner.start_session(customer).unwrap();
|
||||||
|
|
@ -604,7 +600,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_end_session() {
|
fn test_end_session() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
let customer = Customer::default();
|
let customer = Customer::default();
|
||||||
|
|
||||||
let session_id = runner.start_session(customer).unwrap();
|
let session_id = runner.start_session(customer).unwrap();
|
||||||
|
|
@ -616,7 +612,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_process_message() {
|
async fn test_process_message() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
let customer = Customer::default();
|
let customer = Customer::default();
|
||||||
|
|
||||||
let session_id = runner.start_session(customer).unwrap();
|
let session_id = runner.start_session(customer).unwrap();
|
||||||
|
|
@ -629,7 +625,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_process_message_invalid_session() {
|
async fn test_process_message_invalid_session() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
let invalid_session_id = Uuid::new_v4();
|
let invalid_session_id = Uuid::new_v4();
|
||||||
|
|
||||||
let result = runner
|
let result = runner
|
||||||
|
|
@ -642,22 +638,22 @@ mod tests {
|
||||||
assert_eq!(result.state, ConversationState::Error);
|
assert_eq!(result.state, ConversationState::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn test_execute_script() {
|
fn test_execute_script() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
runner.load_script("greeting", "TALK \"Hello\"");
|
runner.load_script("greeting", "TALK \"Hello\"");
|
||||||
|
|
||||||
let result = runner.execute_script("greeting", "Hi").await.unwrap();
|
let result = runner.execute_script("greeting", "Hi").unwrap();
|
||||||
|
|
||||||
assert!(result.response.is_some());
|
assert!(result.response.is_some());
|
||||||
assert!(result.error.is_none());
|
assert!(result.error.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[test]
|
||||||
async fn test_execute_script_not_found() {
|
fn test_execute_script_not_found() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
|
|
||||||
let result = runner.execute_script("nonexistent", "Hi").await.unwrap();
|
let result = runner.execute_script("nonexistent", "Hi").unwrap();
|
||||||
|
|
||||||
assert!(result.response.is_none());
|
assert!(result.response.is_none());
|
||||||
assert!(result.error.is_some());
|
assert!(result.error.is_some());
|
||||||
|
|
@ -675,7 +671,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reset_metrics() {
|
fn test_reset_metrics() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut metrics = runner.metrics.lock().unwrap();
|
let mut metrics = runner.metrics.lock().unwrap();
|
||||||
|
|
@ -709,7 +705,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_session_info() {
|
fn test_session_info() {
|
||||||
let mut runner = BotRunner::new();
|
let runner = BotRunner::new();
|
||||||
let customer = Customer::default();
|
let customer = Customer::default();
|
||||||
let customer_id = customer.id;
|
let customer_id = customer.id;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -171,45 +170,46 @@ impl DesktopApp {
|
||||||
self.platform
|
self.platform
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_window(&self, title: &str) -> Result<Option<WindowHandle>> {
|
pub fn find_window(&self, title: &str) -> Result<Option<WindowHandle>> {
|
||||||
match self.platform {
|
match self.platform {
|
||||||
Platform::Windows => self.find_window_windows(title).await,
|
Platform::Windows => Self::find_window_windows(title),
|
||||||
Platform::MacOS => self.find_window_macos(title).await,
|
Platform::MacOS => Self::find_window_macos(title),
|
||||||
Platform::Linux => self.find_window_linux(title).await,
|
Platform::Linux => Self::find_window_linux(title),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
async fn find_window_windows(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_windows(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("Windows desktop testing not yet implemented")
|
anyhow::bail!("Windows desktop testing not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
async fn find_window_windows(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_windows(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("Windows desktop testing not available on this platform")
|
anyhow::bail!("Windows desktop testing not available on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
async fn find_window_macos(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_macos(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("macOS desktop testing not yet implemented")
|
anyhow::bail!("macOS desktop testing not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
async fn find_window_macos(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_macos(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("macOS desktop testing not available on this platform")
|
anyhow::bail!("macOS desktop testing not available on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
async fn find_window_linux(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_linux(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("Linux desktop testing not yet implemented")
|
anyhow::bail!("Linux desktop testing not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
async fn find_window_linux(&self, _title: &str) -> Result<Option<WindowHandle>> {
|
fn find_window_linux(_title: &str) -> Result<Option<WindowHandle>> {
|
||||||
anyhow::bail!("Linux desktop testing not available on this platform")
|
anyhow::bail!("Linux desktop testing not available on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn screenshot(&self) -> Result<Screenshot> {
|
pub fn screenshot(&self) -> Result<Screenshot> {
|
||||||
|
let _ = &self.platform;
|
||||||
anyhow::bail!("Screenshot functionality not yet implemented")
|
anyhow::bail!("Screenshot functionality not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +255,7 @@ pub struct Screenshot {
|
||||||
impl Screenshot {
|
impl Screenshot {
|
||||||
pub fn save(&self, path: impl Into<PathBuf>) -> Result<()> {
|
pub fn save(&self, path: impl Into<PathBuf>) -> Result<()> {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
anyhow::bail!("Screenshot save not yet implemented: {path:?}")
|
anyhow::bail!("Screenshot save not yet implemented: {}", path.display())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,23 +302,28 @@ pub struct Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
pub async fn click(&self) -> Result<()> {
|
pub fn click(&self) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element click not yet implemented")
|
anyhow::bail!("Element click not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn double_click(&self) -> Result<()> {
|
pub fn double_click(&self) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element double-click not yet implemented")
|
anyhow::bail!("Element double-click not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn right_click(&self) -> Result<()> {
|
pub fn right_click(&self) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element right-click not yet implemented")
|
anyhow::bail!("Element right-click not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn type_text(&self, _text: &str) -> Result<()> {
|
pub fn type_text(&self, _text: &str) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element type_text not yet implemented")
|
anyhow::bail!("Element type_text not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear(&self) -> Result<()> {
|
pub fn clear(&self) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element clear not yet implemented")
|
anyhow::bail!("Element clear not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,7 +337,8 @@ impl Element {
|
||||||
self.bounds.width > 0 && self.bounds.height > 0
|
self.bounds.width > 0 && self.bounds.height > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn focus(&self) -> Result<()> {
|
pub fn focus(&self) -> Result<()> {
|
||||||
|
let _ = &self.locator;
|
||||||
anyhow::bail!("Element focus not yet implemented")
|
anyhow::bail!("Element focus not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,7 @@ impl TestContext {
|
||||||
let user = std::env::var("DB_USER").unwrap_or_else(|_| "gbuser".to_string());
|
let user = std::env::var("DB_USER").unwrap_or_else(|_| "gbuser".to_string());
|
||||||
let password = std::env::var("DB_PASSWORD").unwrap_or_else(|_| "gbuser".to_string());
|
let password = std::env::var("DB_PASSWORD").unwrap_or_else(|_| "gbuser".to_string());
|
||||||
let database = std::env::var("DB_NAME").unwrap_or_else(|_| "botserver".to_string());
|
let database = std::env::var("DB_NAME").unwrap_or_else(|_| "botserver".to_string());
|
||||||
format!(
|
format!("postgres://{user}:{password}@{host}:{port}/{database}")
|
||||||
"postgres://{user}:{password}@{host}:{port}/{database}"
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"postgres://bottest:bottest@127.0.0.1:{}/bottest",
|
"postgres://bottest:bottest@127.0.0.1:{}/bottest",
|
||||||
|
|
@ -706,7 +704,6 @@ impl BotServerInstance {
|
||||||
log::info!("Botserver working directory: {botserver_dir:?}");
|
log::info!("Botserver working directory: {botserver_dir:?}");
|
||||||
log::info!("Stack path (absolute): {stack_path:?}");
|
log::info!("Stack path (absolute): {stack_path:?}");
|
||||||
|
|
||||||
|
|
||||||
let installers_path = botserver_dir.join("botserver-installers");
|
let installers_path = botserver_dir.join("botserver-installers");
|
||||||
let installers_path = installers_path.canonicalize().unwrap_or(installers_path);
|
let installers_path = installers_path.canonicalize().unwrap_or(installers_path);
|
||||||
log::info!("Using installers from: {installers_path:?}");
|
log::info!("Using installers from: {installers_path:?}");
|
||||||
|
|
@ -733,9 +730,7 @@ impl BotServerInstance {
|
||||||
|
|
||||||
if process.is_some() {
|
if process.is_some() {
|
||||||
let max_wait = 600;
|
let max_wait = 600;
|
||||||
log::info!(
|
log::info!("Waiting for botserver to bootstrap and become ready... (max {max_wait}s)");
|
||||||
"Waiting for botserver to bootstrap and become ready... (max {max_wait}s)"
|
|
||||||
);
|
|
||||||
for i in 0..max_wait {
|
for i in 0..max_wait {
|
||||||
if let Ok(resp) = reqwest::get(&format!("{url}/health")).await {
|
if let Ok(resp) = reqwest::get(&format!("{url}/health")).await {
|
||||||
if resp.status().is_success() {
|
if resp.status().is_success() {
|
||||||
|
|
@ -951,7 +946,7 @@ impl TestHarness {
|
||||||
log::info!("Starting PostgreSQL on port {}...", ctx.ports.postgres);
|
log::info!("Starting PostgreSQL on port {}...", ctx.ports.postgres);
|
||||||
let pg = PostgresService::start(ctx.ports.postgres, &data_dir_str).await?;
|
let pg = PostgresService::start(ctx.ports.postgres, &data_dir_str).await?;
|
||||||
if config.run_migrations {
|
if config.run_migrations {
|
||||||
pg.run_migrations().await?;
|
pg.run_migrations()?;
|
||||||
}
|
}
|
||||||
ctx.postgres = Some(pg);
|
ctx.postgres = Some(pg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
src/main.rs
26
src/main.rs
|
|
@ -262,12 +262,7 @@ fn detect_browser_version(browser_path: &str) -> Option<String> {
|
||||||
let parts: Vec<&str> = version_str.split_whitespace().collect();
|
let parts: Vec<&str> = version_str.split_whitespace().collect();
|
||||||
|
|
||||||
for part in parts {
|
for part in parts {
|
||||||
if part.contains('.')
|
if part.contains('.') && part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
|
||||||
&& part
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.is_some_and(|c| c.is_ascii_digit())
|
|
||||||
{
|
|
||||||
let major = part.split('.').next()?;
|
let major = part.split('.').next()?;
|
||||||
return Some(major.to_string());
|
return Some(major.to_string());
|
||||||
}
|
}
|
||||||
|
|
@ -389,9 +384,8 @@ async fn setup_chromedriver(browser_path: &str) -> Result<PathBuf> {
|
||||||
|
|
||||||
let chrome_version = get_chromedriver_version_for_browser(&major_version).await?;
|
let chrome_version = get_chromedriver_version_for_browser(&major_version).await?;
|
||||||
|
|
||||||
let chromedriver_url = format!(
|
let chromedriver_url =
|
||||||
"{CHROMEDRIVER_URL}/{chrome_version}/linux64/chromedriver-linux64.zip"
|
format!("{CHROMEDRIVER_URL}/{chrome_version}/linux64/chromedriver-linux64.zip");
|
||||||
);
|
|
||||||
|
|
||||||
let zip_path = cache_dir.join("chromedriver.zip");
|
let zip_path = cache_dir.join("chromedriver.zip");
|
||||||
download_file(&chromedriver_url, &zip_path).await?;
|
download_file(&chromedriver_url, &zip_path).await?;
|
||||||
|
|
@ -440,9 +434,7 @@ async fn setup_chrome_for_testing() -> Result<PathBuf> {
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|_| "131.0.6778.204".to_string());
|
.unwrap_or_else(|_| "131.0.6778.204".to_string());
|
||||||
|
|
||||||
let chrome_url = format!(
|
let chrome_url = format!("{CHROMEDRIVER_URL}/{chrome_version}/linux64/chrome-linux64.zip");
|
||||||
"{CHROMEDRIVER_URL}/{chrome_version}/linux64/chrome-linux64.zip"
|
|
||||||
);
|
|
||||||
|
|
||||||
let zip_path = cache_dir.join("chrome.zip");
|
let zip_path = cache_dir.join("chrome.zip");
|
||||||
download_file(&chrome_url, &zip_path).await?;
|
download_file(&chrome_url, &zip_path).await?;
|
||||||
|
|
@ -555,7 +547,7 @@ async fn run_browser_demo() -> Result<()> {
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
|
||||||
info!("Closing browser...");
|
info!("Closing browser...");
|
||||||
let _ = browser.close().await;
|
let _ = browser.close();
|
||||||
let _ = browser_service.stop().await;
|
let _ = browser_service.stop().await;
|
||||||
|
|
||||||
info!("Demo complete!");
|
info!("Demo complete!");
|
||||||
|
|
@ -864,9 +856,7 @@ async fn run_e2e_tests(config: &RunnerConfig) -> Result<TestResults> {
|
||||||
let _ = child.kill();
|
let _ = child.kill();
|
||||||
}
|
}
|
||||||
results.failed = 1;
|
results.failed = 1;
|
||||||
results
|
results.errors.push(format!("Botserver start failed: {e}"));
|
||||||
.errors
|
|
||||||
.push(format!("Botserver start failed: {e}"));
|
|
||||||
return Ok(results);
|
return Ok(results);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -917,9 +907,7 @@ async fn run_e2e_tests(config: &RunnerConfig) -> Result<TestResults> {
|
||||||
results.skipped = skipped;
|
results.skipped = skipped;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
results
|
results.errors.push(format!("Failed to run E2E tests: {e}"));
|
||||||
.errors
|
|
||||||
.push(format!("Failed to run E2E tests: {e}"));
|
|
||||||
results.failed = 1;
|
results.failed = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use super::{check_tcp_port, ensure_dir, wait_for, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_TIMEOUT};
|
use super::{check_tcp_port, ensure_dir, wait_for, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_TIMEOUT};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use nix::sys::signal::{kill, Signal};
|
use nix::sys::signal::{kill, Signal};
|
||||||
|
|
@ -32,7 +31,10 @@ impl PostgresService {
|
||||||
let bin_dir = PathBuf::from(&stack_path).join("bin/tables/bin");
|
let bin_dir = PathBuf::from(&stack_path).join("bin/tables/bin");
|
||||||
let lib_dir = PathBuf::from(&stack_path).join("bin/tables/lib");
|
let lib_dir = PathBuf::from(&stack_path).join("bin/tables/lib");
|
||||||
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
||||||
log::info!("Using PostgreSQL from BOTSERVER_STACK_PATH: {bin_dir:?}");
|
log::info!(
|
||||||
|
"Using PostgreSQL from BOTSERVER_STACK_PATH: {}",
|
||||||
|
bin_dir.display()
|
||||||
|
);
|
||||||
return Ok((bin_dir, Some(lib_dir)));
|
return Ok((bin_dir, Some(lib_dir)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +50,10 @@ impl PostgresService {
|
||||||
let bin_dir = cwd.join(rel_path);
|
let bin_dir = cwd.join(rel_path);
|
||||||
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
||||||
let lib_dir = bin_dir.parent().unwrap().join("lib");
|
let lib_dir = bin_dir.parent().unwrap().join("lib");
|
||||||
log::info!("Using PostgreSQL from botserver-stack: {bin_dir:?}");
|
log::info!(
|
||||||
|
"Using PostgreSQL from botserver-stack: {}",
|
||||||
|
bin_dir.display()
|
||||||
|
);
|
||||||
return Ok((
|
return Ok((
|
||||||
bin_dir,
|
bin_dir,
|
||||||
if lib_dir.exists() {
|
if lib_dir.exists() {
|
||||||
|
|
@ -76,14 +81,14 @@ impl PostgresService {
|
||||||
for path in &system_paths {
|
for path in &system_paths {
|
||||||
let bin_dir = PathBuf::from(path);
|
let bin_dir = PathBuf::from(path);
|
||||||
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
if bin_dir.join("postgres").exists() || bin_dir.join("initdb").exists() {
|
||||||
log::info!("Using system PostgreSQL from: {bin_dir:?}");
|
log::info!("Using system PostgreSQL from: {}", bin_dir.display());
|
||||||
return Ok((bin_dir, None));
|
return Ok((bin_dir, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(initdb_path) = which::which("initdb") {
|
if let Ok(initdb_path) = which::which("initdb") {
|
||||||
if let Some(bin_dir) = initdb_path.parent() {
|
if let Some(bin_dir) = initdb_path.parent() {
|
||||||
log::info!("Using PostgreSQL from PATH: {bin_dir:?}");
|
log::info!("Using PostgreSQL from PATH: {}", bin_dir.display());
|
||||||
return Ok((bin_dir.to_path_buf(), None));
|
return Ok((bin_dir.to_path_buf(), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,14 +119,14 @@ impl PostgresService {
|
||||||
service.connection_string = service.build_connection_string();
|
service.connection_string = service.build_connection_string();
|
||||||
|
|
||||||
if !data_path.join("PG_VERSION").exists() {
|
if !data_path.join("PG_VERSION").exists() {
|
||||||
service.init_db().await?;
|
service.init_db()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
service.start_server().await?;
|
service.start_server()?;
|
||||||
|
|
||||||
service.wait_ready().await?;
|
service.wait_ready().await?;
|
||||||
|
|
||||||
service.setup_test_database().await?;
|
service.setup_test_database()?;
|
||||||
|
|
||||||
Ok(service)
|
Ok(service)
|
||||||
}
|
}
|
||||||
|
|
@ -139,10 +144,10 @@ impl PostgresService {
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_db(&self) -> Result<()> {
|
fn init_db(&self) -> Result<()> {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Initializing PostgreSQL data directory at {:?}",
|
"Initializing PostgreSQL data directory at {}",
|
||||||
self.data_dir
|
self.data_dir.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
let output = self
|
let output = self
|
||||||
|
|
@ -207,15 +212,15 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_server(&mut self) -> Result<()> {
|
fn start_server(&mut self) -> Result<()> {
|
||||||
log::info!("Starting PostgreSQL on port {}", self.port);
|
log::info!("Starting PostgreSQL on port {}", self.port);
|
||||||
|
|
||||||
let log_path = self.data_dir.join("postgres.log");
|
let log_path = self.data_dir.join("postgres.log");
|
||||||
let log_file = std::fs::File::create(&log_path)
|
let log_file = std::fs::File::create(&log_path)
|
||||||
.context(format!("Failed to create log file {log_path:?}"))?;
|
.context(format!("Failed to create log file {}", log_path.display()))?;
|
||||||
let stderr_file = log_file.try_clone()?;
|
let stderr_file = log_file.try_clone()?;
|
||||||
|
|
||||||
log::debug!("PostgreSQL log file: {log_path:?}");
|
log::debug!("PostgreSQL log file: {}", log_path.display());
|
||||||
|
|
||||||
let mut cmd = self.build_command("postgres");
|
let mut cmd = self.build_command("postgres");
|
||||||
let child = cmd
|
let child = cmd
|
||||||
|
|
@ -237,14 +242,14 @@ unix_socket_directories = '{}'
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if result.is_err() {
|
if let Err(e) = result {
|
||||||
let log_path = self.data_dir.join("postgres.log");
|
let log_path = self.data_dir.join("postgres.log");
|
||||||
if log_path.exists() {
|
if log_path.exists() {
|
||||||
if let Ok(log_content) = std::fs::read_to_string(&log_path) {
|
if let Ok(log_content) = std::fs::read_to_string(&log_path) {
|
||||||
log::error!("PostgreSQL log:\n{log_content}");
|
log::error!("PostgreSQL log:\n{log_content}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Err(result.unwrap_err()).context("PostgreSQL failed to start in time");
|
return Err(e).context("PostgreSQL failed to start in time");
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..30 {
|
for _ in 0..30 {
|
||||||
|
|
@ -262,7 +267,7 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_test_database(&self) -> Result<()> {
|
fn setup_test_database(&self) -> Result<()> {
|
||||||
log::info!("Setting up test database '{}'", self.database_name);
|
log::info!("Setting up test database '{}'", self.database_name);
|
||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
|
|
@ -302,7 +307,7 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_migrations(&self) -> Result<()> {
|
pub fn run_migrations(&self) -> Result<()> {
|
||||||
log::info!("Running database migrations...");
|
log::info!("Running database migrations...");
|
||||||
|
|
||||||
if let Ok(diesel) = which::which("diesel") {
|
if let Ok(diesel) = which::which("diesel") {
|
||||||
|
|
@ -324,7 +329,7 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_database(&self, name: &str) -> Result<()> {
|
pub fn create_database(&self, name: &str) -> Result<()> {
|
||||||
let output = self
|
let output = self
|
||||||
.build_command("psql")
|
.build_command("psql")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -349,7 +354,7 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(&self, sql: &str) -> Result<()> {
|
pub fn execute(&self, sql: &str) -> Result<()> {
|
||||||
let output = self
|
let output = self
|
||||||
.build_command("psql")
|
.build_command("psql")
|
||||||
.args([
|
.args([
|
||||||
|
|
@ -374,7 +379,7 @@ unix_socket_directories = '{}'
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query(&self, sql: &str) -> Result<String> {
|
pub fn query(&self, sql: &str) -> Result<String> {
|
||||||
let output = self
|
let output = self
|
||||||
.build_command("psql")
|
.build_command("psql")
|
||||||
.args([
|
.args([
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chromiumoxide::browser::{Browser as CdpBrowser, BrowserConfig as CdpBrowserConfig};
|
use chromiumoxide::browser::{Browser as CdpBrowser, BrowserConfig as CdpBrowserConfig};
|
||||||
use chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat;
|
use chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat;
|
||||||
|
|
@ -31,7 +30,7 @@ impl Default for BrowserType {
|
||||||
|
|
||||||
impl BrowserType {
|
impl BrowserType {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn browser_name(&self) -> &'static str {
|
pub const fn browser_name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Chrome => "chrome",
|
Self::Chrome => "chrome",
|
||||||
Self::Firefox => "firefox",
|
Self::Firefox => "firefox",
|
||||||
|
|
@ -41,7 +40,7 @@ impl BrowserType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn capability_name(&self) -> &'static str {
|
pub const fn capability_name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Chrome => "goog:chromeOptions",
|
Self::Chrome => "goog:chromeOptions",
|
||||||
Self::Firefox => "moz:firefoxOptions",
|
Self::Firefox => "moz:firefoxOptions",
|
||||||
|
|
@ -223,7 +222,7 @@ impl BrowserConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Browser {
|
pub struct Browser {
|
||||||
browser: Arc<CdpBrowser>,
|
cdp: Arc<CdpBrowser>,
|
||||||
page: Arc<Mutex<Page>>,
|
page: Arc<Mutex<Page>>,
|
||||||
config: BrowserConfig,
|
config: BrowserConfig,
|
||||||
_handle: tokio::task::JoinHandle<()>,
|
_handle: tokio::task::JoinHandle<()>,
|
||||||
|
|
@ -241,7 +240,11 @@ impl Browser {
|
||||||
.await
|
.await
|
||||||
.context("Failed to parse CDP JSON response")?;
|
.context("Failed to parse CDP JSON response")?;
|
||||||
json.get("webSocketDebuggerUrl")
|
json.get("webSocketDebuggerUrl")
|
||||||
.and_then(|v| v.as_str()).map_or_else(|| format!("ws://127.0.0.1:{}", config.debug_port), std::string::ToString::to_string)
|
.and_then(|v| v.as_str())
|
||||||
|
.map_or_else(
|
||||||
|
|| format!("ws://127.0.0.1:{}", config.debug_port),
|
||||||
|
std::string::ToString::to_string,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => format!("ws://127.0.0.1:{}", config.debug_port),
|
_ => format!("ws://127.0.0.1:{}", config.debug_port),
|
||||||
};
|
};
|
||||||
|
|
@ -255,8 +258,7 @@ impl Browser {
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
match handler.next().await {
|
match handler.next().await {
|
||||||
Some(Ok(())) => {
|
Some(Ok(())) => {}
|
||||||
}
|
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
let err_str = format!("{e:?}");
|
let err_str = format!("{e:?}");
|
||||||
if err_str.contains("did not match any variant") {
|
if err_str.contains("did not match any variant") {
|
||||||
|
|
@ -315,7 +317,7 @@ impl Browser {
|
||||||
log::info!("Successfully connected to browser via CDP");
|
log::info!("Successfully connected to browser via CDP");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
browser: Arc::new(browser),
|
cdp: Arc::new(browser),
|
||||||
page: Arc::new(Mutex::new(page)),
|
page: Arc::new(Mutex::new(page)),
|
||||||
config,
|
config,
|
||||||
_handle: handle,
|
_handle: handle,
|
||||||
|
|
@ -334,8 +336,7 @@ impl Browser {
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
match handler.next().await {
|
match handler.next().await {
|
||||||
Some(Ok(())) => {
|
Some(Ok(())) => {}
|
||||||
}
|
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
let err_str = format!("{e:?}");
|
let err_str = format!("{e:?}");
|
||||||
if err_str.contains("did not match any variant") {
|
if err_str.contains("did not match any variant") {
|
||||||
|
|
@ -375,7 +376,7 @@ impl Browser {
|
||||||
log::info!("Browser launched successfully");
|
log::info!("Browser launched successfully");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
browser: Arc::new(browser),
|
cdp: Arc::new(browser),
|
||||||
page: Arc::new(Mutex::new(page)),
|
page: Arc::new(Mutex::new(page)),
|
||||||
config,
|
config,
|
||||||
_handle: handle,
|
_handle: handle,
|
||||||
|
|
@ -391,77 +392,93 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn goto(&self, url: &str) -> Result<()> {
|
pub async fn goto(&self, url: &str) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
|
||||||
|
|
||||||
let _ = page.bring_to_front().await;
|
|
||||||
|
|
||||||
if url.starts_with("https://") {
|
if url.starts_with("https://") {
|
||||||
log::info!("Using JavaScript navigation for HTTPS URL: {url}");
|
log::info!("Using JavaScript navigation for HTTPS URL: {url}");
|
||||||
|
|
||||||
let _ = page.goto("about:blank").await;
|
{
|
||||||
|
let page = self.page.lock().await;
|
||||||
|
let _ = page.bring_to_front().await;
|
||||||
|
let _ = page.goto("about:blank").await;
|
||||||
|
}
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
|
||||||
let nav_script = format!("window.location.href = '{url}';");
|
{
|
||||||
let _ = page.evaluate(nav_script.as_str()).await;
|
let page = self.page.lock().await;
|
||||||
|
let nav_script = format!("window.location.href = '{url}';");
|
||||||
|
let _ = page.evaluate(nav_script.as_str()).await;
|
||||||
|
}
|
||||||
|
|
||||||
sleep(Duration::from_millis(1500)).await;
|
sleep(Duration::from_millis(1500)).await;
|
||||||
|
|
||||||
if let Ok(Some(current)) = page.url().await {
|
{
|
||||||
if current.as_str() != "about:blank" {
|
let page = self.page.lock().await;
|
||||||
log::info!("Navigation successful: {current}");
|
if let Ok(Some(current)) = page.url().await {
|
||||||
|
if current.as_str() != "about:blank" {
|
||||||
|
log::info!("Navigation successful: {current}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
page.goto(url)
|
{
|
||||||
.await
|
let page = self.page.lock().await;
|
||||||
.context(format!("Failed to navigate to {url}"))?;
|
let _ = page.bring_to_front().await;
|
||||||
|
page.goto(url)
|
||||||
|
.await
|
||||||
|
.context(format!("Failed to navigate to {url}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
sleep(Duration::from_millis(300)).await;
|
sleep(Duration::from_millis(300)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = page.bring_to_front().await;
|
{
|
||||||
|
let page = self.page.lock().await;
|
||||||
let _ = page
|
let _ = page.bring_to_front().await;
|
||||||
.evaluate("window.focus(); document.body.style.visibility = 'visible';")
|
let _ = page
|
||||||
.await;
|
.evaluate("window.focus(); document.body.style.visibility = 'visible';")
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn current_url(&self) -> Result<String> {
|
pub async fn current_url(&self) -> Result<String> {
|
||||||
let page = self.page.lock().await;
|
let url = {
|
||||||
let url = page
|
let page = self.page.lock().await;
|
||||||
.url()
|
page.url()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get current URL")?
|
.context("Failed to get current URL")?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn title(&self) -> Result<String> {
|
pub async fn title(&self) -> Result<String> {
|
||||||
let page = self.page.lock().await;
|
let title = {
|
||||||
let title = page
|
let page = self.page.lock().await;
|
||||||
.get_title()
|
page.get_title()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get page title")?
|
.context("Failed to get page title")?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
Ok(title)
|
Ok(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn page_source(&self) -> Result<String> {
|
pub async fn page_source(&self) -> Result<String> {
|
||||||
let page = self.page.lock().await;
|
let content = {
|
||||||
let content = page.content().await.context("Failed to get page source")?;
|
let page = self.page.lock().await;
|
||||||
|
page.content().await.context("Failed to get page source")?
|
||||||
|
};
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find(&self, locator: Locator) -> Result<Element> {
|
pub async fn find(&self, locator: Locator) -> Result<Element> {
|
||||||
let page = self.page.lock().await;
|
let element = {
|
||||||
let selector = locator.to_css_selector();
|
let page = self.page.lock().await;
|
||||||
|
let selector = locator.to_css_selector();
|
||||||
let element = page
|
page.find_element(&selector)
|
||||||
.find_element(&selector)
|
.await
|
||||||
.await
|
.context(format!("Failed to find element: {locator:?}"))?
|
||||||
.context(format!("Failed to find element: {locator:?}"))?;
|
};
|
||||||
|
|
||||||
Ok(Element {
|
Ok(Element {
|
||||||
inner: element,
|
inner: element,
|
||||||
|
|
@ -470,13 +487,13 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_all(&self, locator: Locator) -> Result<Vec<Element>> {
|
pub async fn find_all(&self, locator: Locator) -> Result<Vec<Element>> {
|
||||||
let page = self.page.lock().await;
|
let elements = {
|
||||||
let selector = locator.to_css_selector();
|
let page = self.page.lock().await;
|
||||||
|
let selector = locator.to_css_selector();
|
||||||
let elements = page
|
page.find_elements(&selector)
|
||||||
.find_elements(&selector)
|
.await
|
||||||
.await
|
.context(format!("Failed to find elements: {locator:?}"))?
|
||||||
.context(format!("Failed to find elements: {locator:?}"))?;
|
};
|
||||||
|
|
||||||
Ok(elements
|
Ok(elements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -504,16 +521,7 @@ impl Browser {
|
||||||
match &condition {
|
match &condition {
|
||||||
WaitCondition::Present | WaitCondition::Visible | WaitCondition::Clickable => {
|
WaitCondition::Present | WaitCondition::Visible | WaitCondition::Clickable => {
|
||||||
if let Ok(elem) = self.find(locator.clone()).await {
|
if let Ok(elem) = self.find(locator.clone()).await {
|
||||||
match &condition {
|
return Ok(elem);
|
||||||
WaitCondition::Present => return Ok(elem),
|
|
||||||
WaitCondition::Visible => {
|
|
||||||
return Ok(elem);
|
|
||||||
}
|
|
||||||
WaitCondition::Clickable => {
|
|
||||||
return Ok(elem);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WaitCondition::NotPresent => {
|
WaitCondition::NotPresent => {
|
||||||
|
|
@ -556,9 +564,7 @@ impl Browser {
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::bail!(
|
anyhow::bail!("Timeout waiting for element {locator:?} with condition {condition:?}")
|
||||||
"Timeout waiting for element {locator:?} with condition {condition:?}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn click(&self, locator: Locator) -> Result<()> {
|
pub async fn click(&self, locator: Locator) -> Result<()> {
|
||||||
|
|
@ -586,11 +592,12 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value> {
|
pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value> {
|
||||||
let page = self.page.lock().await;
|
let result = {
|
||||||
let result = page
|
let page = self.page.lock().await;
|
||||||
.evaluate(script)
|
page.evaluate(script)
|
||||||
.await
|
.await
|
||||||
.context("Failed to execute script")?;
|
.context("Failed to execute script")?
|
||||||
|
};
|
||||||
Ok(result.value().cloned().unwrap_or(serde_json::Value::Null))
|
Ok(result.value().cloned().unwrap_or(serde_json::Value::Null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -603,27 +610,31 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn screenshot(&self) -> Result<Vec<u8>> {
|
pub async fn screenshot(&self) -> Result<Vec<u8>> {
|
||||||
let page = self.page.lock().await;
|
let screenshot = {
|
||||||
let screenshot = page
|
let page = self.page.lock().await;
|
||||||
.screenshot(
|
page.screenshot(
|
||||||
chromiumoxide::page::ScreenshotParams::builder()
|
chromiumoxide::page::ScreenshotParams::builder()
|
||||||
.format(CaptureScreenshotFormat::Png)
|
.format(CaptureScreenshotFormat::Png)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to take screenshot")?;
|
.context("Failed to take screenshot")?
|
||||||
|
};
|
||||||
Ok(screenshot)
|
Ok(screenshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn screenshot_to_file(&self, path: impl Into<PathBuf>) -> Result<()> {
|
pub async fn screenshot_to_file(&self, path: impl Into<PathBuf>) -> Result<()> {
|
||||||
let data = self.screenshot().await?;
|
let data = self.screenshot().await?;
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
std::fs::write(&path, data).context(format!("Failed to write screenshot to {path:?}"))
|
std::fs::write(&path, &data)
|
||||||
|
.context(format!("Failed to write screenshot to {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh(&self) -> Result<()> {
|
pub async fn refresh(&self) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
{
|
||||||
page.reload().await.context("Failed to refresh page")?;
|
let page = self.page.lock().await;
|
||||||
|
page.reload().await.context("Failed to refresh page")?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -638,17 +649,19 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_window_size(&self, width: u32, height: u32) -> Result<()> {
|
pub async fn set_window_size(&self, width: u32, height: u32) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
{
|
||||||
let cmd = chromiumoxide::cdp::browser_protocol::emulation::SetDeviceMetricsOverrideParams::builder()
|
let page = self.page.lock().await;
|
||||||
.width(width)
|
let cmd = chromiumoxide::cdp::browser_protocol::emulation::SetDeviceMetricsOverrideParams::builder()
|
||||||
.height(height)
|
.width(width)
|
||||||
.device_scale_factor(1.0)
|
.height(height)
|
||||||
.mobile(false)
|
.device_scale_factor(1.0)
|
||||||
.build()
|
.mobile(false)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build set window size params: {e}"))?;
|
.build()
|
||||||
page.execute(cmd)
|
.map_err(|e| anyhow::anyhow!("Failed to build set window size params: {e}"))?;
|
||||||
.await
|
page.execute(cmd)
|
||||||
.context("Failed to set window size")?;
|
.await
|
||||||
|
.context("Failed to set window size")?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -657,8 +670,10 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
|
pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
|
||||||
let page = self.page.lock().await;
|
let cookies = {
|
||||||
let cookies = page.get_cookies().await.context("Failed to get cookies")?;
|
let page = self.page.lock().await;
|
||||||
|
page.get_cookies().await.context("Failed to get cookies")?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(cookies
|
Ok(cookies
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -676,44 +691,45 @@ impl Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_cookie(&self, cookie: Cookie) -> Result<()> {
|
pub async fn set_cookie(&self, cookie: Cookie) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
{
|
||||||
page.set_cookie(
|
let page = self.page.lock().await;
|
||||||
chromiumoxide::cdp::browser_protocol::network::CookieParam::builder()
|
page.set_cookie(
|
||||||
.name(cookie.name)
|
chromiumoxide::cdp::browser_protocol::network::CookieParam::builder()
|
||||||
.value(cookie.value)
|
.name(cookie.name)
|
||||||
.build()
|
.value(cookie.value)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build cookie: {e}"))?,
|
.build()
|
||||||
)
|
.map_err(|e| anyhow::anyhow!("Failed to build cookie: {e}"))?,
|
||||||
.await
|
)
|
||||||
.context("Failed to set cookie")?;
|
.await
|
||||||
|
.context("Failed to set cookie")?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_cookie(&self, name: &str) -> Result<()> {
|
pub async fn delete_cookie(&self, name: &str) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
{
|
||||||
page.delete_cookie(
|
let page = self.page.lock().await;
|
||||||
chromiumoxide::cdp::browser_protocol::network::DeleteCookiesParams::builder()
|
let cmd = chromiumoxide::cdp::browser_protocol::network::DeleteCookiesParams::builder()
|
||||||
.name(name)
|
.name(name)
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build delete cookie params: {e}"))?,
|
.map_err(|e| anyhow::anyhow!("Failed to build delete cookie params: {e}"))?;
|
||||||
)
|
page.execute(cmd).await.context("Failed to delete cookie")?;
|
||||||
.await
|
}
|
||||||
.context("Failed to delete cookie")?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_all_cookies(&self) -> Result<()> {
|
pub async fn delete_all_cookies(&self) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
let cookies = {
|
||||||
let cookies = page.get_cookies().await?;
|
let page = self.page.lock().await;
|
||||||
|
page.get_cookies().await?
|
||||||
|
};
|
||||||
for c in cookies {
|
for c in cookies {
|
||||||
page.delete_cookie(
|
let page = self.page.lock().await;
|
||||||
chromiumoxide::cdp::browser_protocol::network::DeleteCookiesParams::builder()
|
let cmd = chromiumoxide::cdp::browser_protocol::network::DeleteCookiesParams::builder()
|
||||||
.name(&c.name)
|
.name(&c.name)
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build delete cookie params: {e}"))?,
|
.map_err(|e| anyhow::anyhow!("Failed to build delete cookie params: {e}"))?;
|
||||||
)
|
page.execute(cmd).await.ok();
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -745,20 +761,25 @@ impl Browser {
|
||||||
elem.is_displayed().await
|
elem.is_displayed().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close(self) -> Result<()> {
|
pub fn close(self) -> Result<()> {
|
||||||
|
let _ = self.cdp;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_key(&self, key: Key) -> Result<()> {
|
pub async fn send_key(&self, key: Key) -> Result<()> {
|
||||||
let page = self.page.lock().await;
|
|
||||||
let key_str = Self::key_to_cdp_key(key);
|
let key_str = Self::key_to_cdp_key(key);
|
||||||
if let Ok(cmd) =
|
|
||||||
chromiumoxide::cdp::browser_protocol::input::DispatchKeyEventParams::builder()
|
|
||||||
.r#type(chromiumoxide::cdp::browser_protocol::input::DispatchKeyEventType::KeyDown)
|
|
||||||
.text(key_str)
|
|
||||||
.build()
|
|
||||||
{
|
{
|
||||||
let _ = page.execute(cmd).await;
|
let page = self.page.lock().await;
|
||||||
|
if let Ok(cmd) =
|
||||||
|
chromiumoxide::cdp::browser_protocol::input::DispatchKeyEventParams::builder()
|
||||||
|
.r#type(
|
||||||
|
chromiumoxide::cdp::browser_protocol::input::DispatchKeyEventType::KeyDown,
|
||||||
|
)
|
||||||
|
.text(key_str)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
let _ = page.execute(cmd).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -767,42 +788,63 @@ impl Browser {
|
||||||
match key {
|
match key {
|
||||||
Key::Enter => "\r",
|
Key::Enter => "\r",
|
||||||
Key::Tab => "\t",
|
Key::Tab => "\t",
|
||||||
Key::Escape => "",
|
Key::Escape
|
||||||
Key::Backspace => "",
|
| Key::Backspace
|
||||||
Key::Delete => "",
|
| Key::Delete
|
||||||
Key::ArrowUp => "",
|
| Key::ArrowUp
|
||||||
Key::ArrowDown => "",
|
| Key::ArrowDown
|
||||||
Key::ArrowLeft => "",
|
| Key::ArrowLeft
|
||||||
Key::ArrowRight => "",
|
| Key::ArrowRight
|
||||||
Key::Home => "",
|
| Key::Home
|
||||||
Key::End => "",
|
| Key::End
|
||||||
Key::PageUp => "",
|
| Key::PageUp
|
||||||
Key::PageDown => "",
|
| Key::PageDown
|
||||||
_ => "",
|
| Key::F1
|
||||||
|
| Key::F2
|
||||||
|
| Key::F3
|
||||||
|
| Key::F4
|
||||||
|
| Key::F5
|
||||||
|
| Key::F6
|
||||||
|
| Key::F7
|
||||||
|
| Key::F8
|
||||||
|
| Key::F9
|
||||||
|
| Key::F10
|
||||||
|
| Key::F11
|
||||||
|
| Key::F12
|
||||||
|
| Key::Shift
|
||||||
|
| Key::Control
|
||||||
|
| Key::Alt
|
||||||
|
| Key::Meta => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_to_frame(&self, _locator: Locator) -> Result<()> {
|
pub fn switch_to_frame(&self, _locator: Locator) -> Result<()> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_to_frame_by_index(&self, _index: u16) -> Result<()> {
|
pub fn switch_to_frame_by_index(&self, _index: u16) -> Result<()> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_to_parent_frame(&self) -> Result<()> {
|
pub fn switch_to_parent_frame(&self) -> Result<()> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_to_default_content(&self) -> Result<()> {
|
pub fn switch_to_default_content(&self) -> Result<()> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn current_window_handle(&self) -> Result<String> {
|
pub fn current_window_handle(&self) -> Result<String> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok("main".to_string())
|
Ok("main".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn window_handles(&self) -> Result<Vec<String>> {
|
pub fn window_handles(&self) -> Result<Vec<String>> {
|
||||||
|
let _ = &self.page;
|
||||||
Ok(vec!["main".to_string()])
|
Ok(vec!["main".to_string()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -869,7 +911,8 @@ impl Element {
|
||||||
.context(format!("Failed to get attribute {name}"))
|
.context(format!("Failed to get attribute {name}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn css_value(&self, _name: &str) -> Result<String> {
|
pub fn css_value(&self, _name: &str) -> Result<String> {
|
||||||
|
let _ = &self.inner;
|
||||||
Ok(String::new())
|
Ok(String::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -887,7 +930,8 @@ impl Element {
|
||||||
Ok(checked.is_some())
|
Ok(checked.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn tag_name(&self) -> Result<String> {
|
pub fn tag_name(&self) -> Result<String> {
|
||||||
|
let _ = &self.inner;
|
||||||
Ok("element".to_string())
|
Ok("element".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -896,7 +940,8 @@ impl Element {
|
||||||
Ok((point.x as i64, point.y as i64))
|
Ok((point.x as i64, point.y as i64))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn size(&self) -> Result<(u64, u64)> {
|
pub fn size(&self) -> Result<(u64, u64)> {
|
||||||
|
let _ = &self.inner;
|
||||||
Ok((100, 20))
|
Ok((100, 20))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ async fn is_service_running(url: &str) -> bool {
|
||||||
|
|
||||||
impl E2ETestContext {
|
impl E2ETestContext {
|
||||||
pub async fn setup() -> anyhow::Result<Self> {
|
pub async fn setup() -> anyhow::Result<Self> {
|
||||||
|
|
||||||
let botserver_url =
|
let botserver_url =
|
||||||
std::env::var("BOTSERVER_URL").unwrap_or_else(|_| "https://localhost:8080".to_string());
|
std::env::var("BOTSERVER_URL").unwrap_or_else(|_| "https://localhost:8080".to_string());
|
||||||
let botui_url =
|
let botui_url =
|
||||||
|
|
@ -86,7 +85,6 @@ impl E2ETestContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn setup_with_browser() -> anyhow::Result<Self> {
|
pub async fn setup_with_browser() -> anyhow::Result<Self> {
|
||||||
|
|
||||||
let botserver_url =
|
let botserver_url =
|
||||||
std::env::var("BOTSERVER_URL").unwrap_or_else(|_| "https://localhost:8080".to_string());
|
std::env::var("BOTSERVER_URL").unwrap_or_else(|_| "https://localhost:8080".to_string());
|
||||||
let botui_url =
|
let botui_url =
|
||||||
|
|
@ -183,7 +181,7 @@ impl E2ETestContext {
|
||||||
|
|
||||||
pub async fn close(mut self) {
|
pub async fn close(mut self) {
|
||||||
if let Some(browser) = self.browser {
|
if let Some(browser) = self.browser {
|
||||||
let _ = browser.close().await;
|
let _ = browser.close();
|
||||||
}
|
}
|
||||||
if let Some(mut bs) = self.browser_service.take() {
|
if let Some(mut bs) = self.browser_service.take() {
|
||||||
let _ = bs.stop().await;
|
let _ = bs.stop().await;
|
||||||
|
|
@ -322,7 +320,6 @@ async fn test_full_harness_has_all_services() {
|
||||||
"MockZitadel should be available"
|
"MockZitadel should be available"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
assert!(ctx.data_dir.exists());
|
assert!(ctx.data_dir.exists());
|
||||||
assert!(ctx.data_dir.to_str().unwrap().contains("bottest-"));
|
assert!(ctx.data_dir.to_str().unwrap().contains("bottest-"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_module_exports() {
|
|
||||||
|
|
||||||
let _config = KeywordConfig::default();
|
|
||||||
let _parser = KeywordParser::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_respond_request_parse() {
|
|
||||||
let json = r#"{
|
|
||||||
"session_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
"message": "Hello, how can I help?",
|
|
||||||
"attendant_id": "att-001"
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let request: AttendantRespondRequest = serde_json::from_str(json).unwrap();
|
|
||||||
assert_eq!(request.attendant_id, "att-001");
|
|
||||||
assert_eq!(request.message, "Hello, how can I help?");
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_config_defaults() {
|
|
||||||
let config = LlmAssistConfig::default();
|
|
||||||
assert!(!config.tips_enabled);
|
|
||||||
assert!(!config.polish_enabled);
|
|
||||||
assert!(!config.any_enabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_fallback_tips_urgent() {
|
|
||||||
let tips = generate_fallback_tips("This is URGENT! I need help immediately!");
|
|
||||||
assert!(!tips.is_empty());
|
|
||||||
assert!(tips.iter().any(|t| matches!(t.tip_type, TipType::Warning)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_fallback_tips_question() {
|
|
||||||
let tips = generate_fallback_tips("How do I reset my password?");
|
|
||||||
assert!(!tips.is_empty());
|
|
||||||
assert!(tips.iter().any(|t| matches!(t.tip_type, TipType::Intent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sentiment_positive() {
|
|
||||||
let sentiment = analyze_sentiment_keywords("Thank you so much! This is great!");
|
|
||||||
assert_eq!(sentiment.overall, "positive");
|
|
||||||
assert!(sentiment.score > 0.0);
|
|
||||||
assert_eq!(sentiment.escalation_risk, "low");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sentiment_negative() {
|
|
||||||
let sentiment =
|
|
||||||
analyze_sentiment_keywords("This is terrible! I'm very frustrated with this problem.");
|
|
||||||
assert_eq!(sentiment.overall, "negative");
|
|
||||||
assert!(sentiment.score < 0.0);
|
|
||||||
assert!(sentiment.escalation_risk == "medium" || sentiment.escalation_risk == "high");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sentiment_urgent() {
|
|
||||||
let sentiment = analyze_sentiment_keywords("I need help ASAP! This is urgent!");
|
|
||||||
assert!(sentiment.urgency == "high" || sentiment.urgency == "urgent");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_json() {
|
|
||||||
let response = "Here is the result: {\"key\": \"value\"} and some more text.";
|
|
||||||
let json = extract_json(response);
|
|
||||||
assert_eq!(json, "{\"key\": \"value\"}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_fallback_replies() {
|
|
||||||
let replies = generate_fallback_replies();
|
|
||||||
assert_eq!(replies.len(), 3);
|
|
||||||
assert!(replies.iter().any(|r| r.category == "greeting"));
|
|
||||||
assert!(replies.iter().any(|r| r.category == "follow_up"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_help_text() {
|
|
||||||
let help = get_help_text();
|
|
||||||
assert!(help.contains("/queue"));
|
|
||||||
assert!(help.contains("/tips"));
|
|
||||||
assert!(help.contains("/polish"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
mod attendance_llm_assist;
|
|
||||||
mod attendance;
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_label_line() {
|
|
||||||
assert!(is_label_line("start:"));
|
|
||||||
assert!(is_label_line(" mainLoop:"));
|
|
||||||
assert!(is_label_line("my_label:"));
|
|
||||||
assert!(is_label_line("label123:"));
|
|
||||||
|
|
||||||
assert!(!is_label_line("TALK \"hello:\""));
|
|
||||||
assert!(!is_label_line("' comment:"));
|
|
||||||
assert!(!is_label_line("CASE:"));
|
|
||||||
assert!(!is_label_line("123label:"));
|
|
||||||
assert!(!is_label_line("has space:"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_goto_target() {
|
|
||||||
assert_eq!(extract_goto_target("GOTO start"), Some("start".to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
extract_goto_target(" GOTO myLabel"),
|
|
||||||
Some("myLabel".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
extract_goto_target("IF x > 5 THEN GOTO done"),
|
|
||||||
Some("done".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(extract_goto_target("TALK \"hello\""), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_line_simple_goto() {
|
|
||||||
assert_eq!(
|
|
||||||
transform_line("GOTO start"),
|
|
||||||
"__goto_label = \"start\"; continue;"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
transform_line(" GOTO myLoop "),
|
|
||||||
"__goto_label = \"myLoop\"; continue;"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_line_if_then_goto() {
|
|
||||||
let result = transform_line("IF x < 10 THEN GOTO start");
|
|
||||||
assert!(result.contains("if x < 10"));
|
|
||||||
assert!(result.contains("__goto_label = \"start\""));
|
|
||||||
assert!(result.contains("continue"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_line_if_goto_no_then() {
|
|
||||||
let result = transform_line("IF x < 10 GOTO start");
|
|
||||||
assert!(result.contains("if x < 10"));
|
|
||||||
assert!(result.contains("__goto_label = \"start\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_line_not_goto() {
|
|
||||||
assert_eq!(transform_line("TALK \"Hello\""), "TALK \"Hello\"");
|
|
||||||
assert_eq!(transform_line("x = x + 1"), "x = x + 1");
|
|
||||||
assert_eq!(transform_line("ON ERROR GOTO 0"), "ON ERROR GOTO 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_has_goto_constructs() {
|
|
||||||
assert!(has_goto_constructs("start:\nTALK \"hi\"\nGOTO start"));
|
|
||||||
assert!(has_goto_constructs("IF x > 0 THEN GOTO done"));
|
|
||||||
assert!(!has_goto_constructs("TALK \"hello\"\nWAIT 1"));
|
|
||||||
assert!(!has_goto_constructs("ON ERROR GOTO 0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_goto_simple() {
|
|
||||||
let input = r#"start:
|
|
||||||
TALK "Hello"
|
|
||||||
x = x + 1
|
|
||||||
IF x < 3 THEN GOTO start
|
|
||||||
TALK "Done""#;
|
|
||||||
|
|
||||||
let output = transform_goto(input);
|
|
||||||
|
|
||||||
assert!(output.contains("__goto_label"));
|
|
||||||
assert!(output.contains("while"));
|
|
||||||
assert!(output.contains("\"start\""));
|
|
||||||
assert!(output.contains("WARNING"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_goto_no_goto() {
|
|
||||||
let input = "TALK \"Hello\"\nTALK \"World\"";
|
|
||||||
let output = transform_goto(input);
|
|
||||||
assert_eq!(output, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transform_goto_multiple_labels() {
|
|
||||||
let input = r#"start:
|
|
||||||
TALK "Start"
|
|
||||||
GOTO middle
|
|
||||||
middle:
|
|
||||||
TALK "Middle"
|
|
||||||
GOTO done
|
|
||||||
done:
|
|
||||||
TALK "Done""#;
|
|
||||||
|
|
||||||
let output = transform_goto(input);
|
|
||||||
|
|
||||||
assert!(output.contains("\"start\""));
|
|
||||||
assert!(output.contains("\"middle\""));
|
|
||||||
assert!(output.contains("\"done\""));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_infinite_loop_protection() {
|
|
||||||
let output = transform_goto("loop:\nGOTO loop");
|
|
||||||
assert!(output.contains("__goto_max_iterations"));
|
|
||||||
assert!(output.contains("throw"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_a2a_message_creation() {
|
|
||||||
let msg = A2AMessage::new(
|
|
||||||
"bot_a",
|
|
||||||
Some("bot_b"),
|
|
||||||
A2AMessageType::Request,
|
|
||||||
serde_json::json!({"test": "data"}),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(msg.from_agent, "bot_a");
|
|
||||||
assert_eq!(msg.to_agent, Some("bot_b".to_string()));
|
|
||||||
assert_eq!(msg.message_type, A2AMessageType::Request);
|
|
||||||
assert_eq!(msg.hop_count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_a2a_message_response() {
|
|
||||||
let original = A2AMessage::new(
|
|
||||||
"bot_a",
|
|
||||||
Some("bot_b"),
|
|
||||||
A2AMessageType::Request,
|
|
||||||
serde_json::json!({"question": "test"}),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let response = original.create_response("bot_b", serde_json::json!({"answer": "result"}));
|
|
||||||
|
|
||||||
assert_eq!(response.from_agent, "bot_b");
|
|
||||||
assert_eq!(response.to_agent, Some("bot_a".to_string()));
|
|
||||||
assert_eq!(response.message_type, A2AMessageType::Response);
|
|
||||||
assert_eq!(response.correlation_id, original.correlation_id);
|
|
||||||
assert_eq!(response.hop_count, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_message_type_display() {
|
|
||||||
assert_eq!(A2AMessageType::Request.to_string(), "request");
|
|
||||||
assert_eq!(A2AMessageType::Response.to_string(), "response");
|
|
||||||
assert_eq!(A2AMessageType::Broadcast.to_string(), "broadcast");
|
|
||||||
assert_eq!(A2AMessageType::Delegate.to_string(), "delegate");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_message_type_from_str() {
|
|
||||||
assert_eq!(A2AMessageType::from("request"), A2AMessageType::Request);
|
|
||||||
assert_eq!(A2AMessageType::from("RESPONSE"), A2AMessageType::Response);
|
|
||||||
assert_eq!(A2AMessageType::from("unknown"), A2AMessageType::Request);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_a2a_config_default() {
|
|
||||||
let config = A2AConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.timeout_seconds, 30);
|
|
||||||
assert_eq!(config.max_hops, 5);
|
|
||||||
assert_eq!(config.protocol_version, "1.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_message_not_expired() {
|
|
||||||
let msg = A2AMessage::new(
|
|
||||||
"bot_a",
|
|
||||||
Some("bot_b"),
|
|
||||||
A2AMessageType::Request,
|
|
||||||
serde_json::json!({}),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!msg.is_expired());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_max_hops_not_exceeded() {
|
|
||||||
let msg = A2AMessage::new(
|
|
||||||
"bot_a",
|
|
||||||
Some("bot_b"),
|
|
||||||
A2AMessageType::Request,
|
|
||||||
serde_json::json!({}),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!msg.max_hops_exceeded(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_max_hops_exceeded() {
|
|
||||||
let mut msg = A2AMessage::new(
|
|
||||||
"bot_a",
|
|
||||||
Some("bot_b"),
|
|
||||||
A2AMessageType::Request,
|
|
||||||
serde_json::json!({}),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
msg.hop_count = 5;
|
|
||||||
|
|
||||||
assert!(msg.max_hops_exceeded(5));
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_trigger_from_keywords() {
|
|
||||||
let trigger = BotTrigger::from_keywords(vec!["finance".to_string(), "money".to_string()]);
|
|
||||||
assert_eq!(trigger.trigger_type, TriggerType::Keyword);
|
|
||||||
assert_eq!(trigger.keywords.unwrap().len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_match_bot_triggers() {
|
|
||||||
let bots = vec![
|
|
||||||
SessionBot {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
session_id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
bot_name: "finance-bot".to_string(),
|
|
||||||
trigger: BotTrigger::from_keywords(vec!["money".to_string(), "budget".to_string()]),
|
|
||||||
priority: 1,
|
|
||||||
is_active: true,
|
|
||||||
},
|
|
||||||
SessionBot {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
session_id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
bot_name: "hr-bot".to_string(),
|
|
||||||
trigger: BotTrigger::from_keywords(vec![
|
|
||||||
"vacation".to_string(),
|
|
||||||
"employee".to_string(),
|
|
||||||
]),
|
|
||||||
priority: 0,
|
|
||||||
is_active: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let matches = match_bot_triggers("How much money do I have?", &bots);
|
|
||||||
assert_eq!(matches.len(), 1);
|
|
||||||
assert_eq!(matches[0].bot_name, "finance-bot");
|
|
||||||
|
|
||||||
let matches = match_bot_triggers("I need to request vacation", &bots);
|
|
||||||
assert_eq!(matches.len(), 1);
|
|
||||||
assert_eq!(matches[0].bot_name, "hr-bot");
|
|
||||||
|
|
||||||
let matches = match_bot_triggers("Hello world", &bots);
|
|
||||||
assert!(matches.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_match_tool_triggers() {
|
|
||||||
let bots = vec![SessionBot {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
session_id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
bot_name: "data-bot".to_string(),
|
|
||||||
trigger: BotTrigger::from_tools(vec!["AGGREGATE".to_string(), "CHART".to_string()]),
|
|
||||||
priority: 1,
|
|
||||||
is_active: true,
|
|
||||||
}];
|
|
||||||
|
|
||||||
let matches = match_tool_triggers("aggregate", &bots);
|
|
||||||
assert_eq!(matches.len(), 1);
|
|
||||||
|
|
||||||
let matches = match_tool_triggers("SEND", &bots);
|
|
||||||
assert!(matches.is_empty());
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_role() {
|
|
||||||
assert_eq!(validate_role("admin"), "admin");
|
|
||||||
assert_eq!(validate_role("ADMIN"), "admin");
|
|
||||||
assert_eq!(validate_role("contributor"), "contributor");
|
|
||||||
assert_eq!(validate_role("viewer"), "viewer");
|
|
||||||
assert_eq!(validate_role("unknown"), "member");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_permissions_for_role() {
|
|
||||||
let admin_perms = get_permissions_for_role("admin");
|
|
||||||
assert!(admin_perms.get("read").unwrap().as_bool().unwrap());
|
|
||||||
assert!(admin_perms.get("write").unwrap().as_bool().unwrap());
|
|
||||||
assert!(admin_perms
|
|
||||||
.get("manage_members")
|
|
||||||
.unwrap()
|
|
||||||
.as_bool()
|
|
||||||
.unwrap());
|
|
||||||
|
|
||||||
let viewer_perms = get_permissions_for_role("viewer");
|
|
||||||
assert!(viewer_perms.get("read").unwrap().as_bool().unwrap());
|
|
||||||
assert!(!viewer_perms.get("write").unwrap().as_bool().unwrap());
|
|
||||||
assert!(!viewer_perms.get("delete").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_suggestion_json_context() {
|
|
||||||
let suggestion = json!({
|
|
||||||
"type": "context",
|
|
||||||
"context": "products",
|
|
||||||
"text": "View Products",
|
|
||||||
"action": {
|
|
||||||
"type": "select_context",
|
|
||||||
"context": "products"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(suggestion["type"], "context");
|
|
||||||
assert_eq!(suggestion["action"]["type"], "select_context");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_suggestion_json_tool_no_params() {
|
|
||||||
let suggestion = json!({
|
|
||||||
"type": "tool",
|
|
||||||
"tool": "search_kb",
|
|
||||||
"text": "Search Knowledge Base",
|
|
||||||
"action": {
|
|
||||||
"type": "invoke_tool",
|
|
||||||
"tool": "search_kb",
|
|
||||||
"params": Option::<Vec<String>>::None,
|
|
||||||
"prompt_for_params": true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(suggestion["type"], "tool");
|
|
||||||
assert_eq!(suggestion["action"]["prompt_for_params"], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_suggestion_json_tool_with_params() {
|
|
||||||
let params = vec!["query".to_string(), "products".to_string()];
|
|
||||||
let suggestion = json!({
|
|
||||||
"type": "tool",
|
|
||||||
"tool": "search_kb",
|
|
||||||
"text": "Search Products",
|
|
||||||
"action": {
|
|
||||||
"type": "invoke_tool",
|
|
||||||
"tool": "search_kb",
|
|
||||||
"params": params,
|
|
||||||
"prompt_for_params": false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(suggestion["type"], "tool");
|
|
||||||
assert_eq!(suggestion["action"]["prompt_for_params"], false);
|
|
||||||
assert!(suggestion["action"]["params"].is_array());
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
use botserver::basic::keywords::agent_reflection::{
|
|
||||||
extract_insights_from_text, ReflectionConfig, ReflectionResult, ReflectionType,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_type_from_str() {
|
|
||||||
assert_eq!(
|
|
||||||
ReflectionType::from("conversation_quality"),
|
|
||||||
ReflectionType::ConversationQuality
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ReflectionType::from("quality"),
|
|
||||||
ReflectionType::ConversationQuality
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ReflectionType::from("tool_usage"),
|
|
||||||
ReflectionType::ToolUsage
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ReflectionType::from("performance"),
|
|
||||||
ReflectionType::Performance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_config_default() {
|
|
||||||
let config = ReflectionConfig::default();
|
|
||||||
assert!(!config.enabled);
|
|
||||||
assert_eq!(config.interval, 10);
|
|
||||||
assert!(!config.auto_apply);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_result_new() {
|
|
||||||
let bot_id = Uuid::new_v4();
|
|
||||||
let session_id = Uuid::new_v4();
|
|
||||||
let result = ReflectionResult::new(bot_id, session_id, ReflectionType::ConversationQuality);
|
|
||||||
|
|
||||||
assert_eq!(result.bot_id, bot_id);
|
|
||||||
assert_eq!(result.session_id, session_id);
|
|
||||||
assert_eq!(result.score, 0.0);
|
|
||||||
assert!(result.insights.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_result_from_json() {
|
|
||||||
let json_response = r#"{
|
|
||||||
"score": 7.5,
|
|
||||||
"key_insights": ["Users prefer concise responses", "Technical questions need more detail"],
|
|
||||||
"improvements": ["Add more examples", "Improve response time"],
|
|
||||||
"positive_patterns": ["Good greeting", "Clear explanations"]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let result = ReflectionResult::from_llm_response(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
ReflectionType::ConversationQuality,
|
|
||||||
json_response,
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(result.score, 7.5);
|
|
||||||
assert_eq!(result.insights.len(), 2);
|
|
||||||
assert_eq!(result.improvements.len(), 2);
|
|
||||||
assert_eq!(result.positive_patterns.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_result_needs_improvement() {
|
|
||||||
let mut result =
|
|
||||||
ReflectionResult::new(Uuid::new_v4(), Uuid::new_v4(), ReflectionType::Performance);
|
|
||||||
|
|
||||||
result.score = 5.0;
|
|
||||||
assert!(result.needs_improvement(6.0));
|
|
||||||
|
|
||||||
result.score = 8.0;
|
|
||||||
assert!(!result.needs_improvement(6.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_insights_from_text() {
|
|
||||||
let text = "Here are some insights:\n\
|
|
||||||
1. Users prefer short responses\n\
|
|
||||||
2. Technical questions need examples\n\
|
|
||||||
- Consider adding more context\n\
|
|
||||||
• Improve response time";
|
|
||||||
|
|
||||||
let insights = extract_insights_from_text(text);
|
|
||||||
assert!(!insights.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_type_prompt_template() {
|
|
||||||
let template = ReflectionType::ConversationQuality.prompt_template();
|
|
||||||
assert!(template.contains("{conversation}"));
|
|
||||||
assert!(template.contains("JSON format"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_reflection_result_summary() {
|
|
||||||
let mut result =
|
|
||||||
ReflectionResult::new(Uuid::new_v4(), Uuid::new_v4(), ReflectionType::Performance);
|
|
||||||
result.score = 7.5;
|
|
||||||
result.messages_analyzed = 15;
|
|
||||||
result.insights = vec!["Insight 1".to_string(), "Insight 2".to_string()];
|
|
||||||
result.improvements = vec!["Improvement 1".to_string()];
|
|
||||||
|
|
||||||
let summary = result.summary();
|
|
||||||
assert!(summary.contains("7.5"));
|
|
||||||
assert!(summary.contains("15"));
|
|
||||||
assert!(summary.contains("2"));
|
|
||||||
assert!(summary.contains("1"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_ubound() {
|
|
||||||
let arr: Vec<Dynamic> = vec![Dynamic::from(1), Dynamic::from(2), Dynamic::from(3)];
|
|
||||||
assert_eq!(arr.len() - 1, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_join() {
|
|
||||||
let arr = vec!["a", "b", "c"];
|
|
||||||
let result = arr.join("-");
|
|
||||||
assert_eq!(result, "a-b-c");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_split() {
|
|
||||||
let s = "a,b,c";
|
|
||||||
let parts: Vec<&str> = s.split(',').collect();
|
|
||||||
assert_eq!(parts.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_range() {
|
|
||||||
let range: Vec<i64> = (1..=5).collect();
|
|
||||||
assert_eq!(range, vec![1, 2, 3, 4, 5]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_flatten() {
|
|
||||||
|
|
||||||
let nested = vec![vec![1, 2], vec![3, 4]];
|
|
||||||
let flat: Vec<i32> = nested.into_iter().flatten().collect();
|
|
||||||
assert_eq!(flat, vec![1, 2, 3, 4]);
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_contains_string() {
|
|
||||||
let arr: Array = vec![
|
|
||||||
Dynamic::from("Alice"),
|
|
||||||
Dynamic::from("Bob"),
|
|
||||||
Dynamic::from("Charlie"),
|
|
||||||
];
|
|
||||||
|
|
||||||
assert!(array_contains(&arr, &Dynamic::from("Bob")));
|
|
||||||
assert!(!array_contains(&arr, &Dynamic::from("David")));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_contains_integer() {
|
|
||||||
let arr: Array = vec![
|
|
||||||
Dynamic::from(1_i64),
|
|
||||||
Dynamic::from(2_i64),
|
|
||||||
Dynamic::from(3_i64),
|
|
||||||
];
|
|
||||||
|
|
||||||
assert!(array_contains(&arr, &Dynamic::from(2_i64)));
|
|
||||||
assert!(!array_contains(&arr, &Dynamic::from(5_i64)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_contains_float() {
|
|
||||||
let arr: Array = vec![
|
|
||||||
Dynamic::from(1.5_f64),
|
|
||||||
Dynamic::from(2.5_f64),
|
|
||||||
Dynamic::from(3.5_f64),
|
|
||||||
];
|
|
||||||
|
|
||||||
assert!(array_contains(&arr, &Dynamic::from(2.5_f64)));
|
|
||||||
assert!(!array_contains(&arr, &Dynamic::from(4.5_f64)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_contains_bool() {
|
|
||||||
let arr: Array = vec![Dynamic::from(true), Dynamic::from(false)];
|
|
||||||
|
|
||||||
assert!(array_contains(&arr, &Dynamic::from(true)));
|
|
||||||
assert!(array_contains(&arr, &Dynamic::from(false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_contains_empty_array() {
|
|
||||||
let arr = Array::new();
|
|
||||||
assert!(!array_contains(&arr, &Dynamic::from("anything")));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_items_equal_integers() {
|
|
||||||
assert!(items_equal(&Dynamic::from(5_i64), &Dynamic::from(5_i64)));
|
|
||||||
assert!(!items_equal(&Dynamic::from(5_i64), &Dynamic::from(6_i64)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_items_equal_strings() {
|
|
||||||
assert!(items_equal(
|
|
||||||
&Dynamic::from("hello"),
|
|
||||||
&Dynamic::from("hello")
|
|
||||||
));
|
|
||||||
assert!(!items_equal(
|
|
||||||
&Dynamic::from("hello"),
|
|
||||||
&Dynamic::from("world")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use rhai::{Array, Dynamic};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_push() {
|
|
||||||
let mut arr: Array = vec![Dynamic::from(1), Dynamic::from(2)];
|
|
||||||
arr.push(Dynamic::from(3));
|
|
||||||
assert_eq!(arr.len(), 3);
|
|
||||||
assert_eq!(arr[2].as_int().unwrap_or(0), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_pop() {
|
|
||||||
let mut arr: Array = vec![Dynamic::from(1), Dynamic::from(2), Dynamic::from(3)];
|
|
||||||
let popped = arr.pop();
|
|
||||||
assert_eq!(arr.len(), 2);
|
|
||||||
assert_eq!(popped.and_then(|v| v.as_int().ok()).unwrap_or(0), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_pop_empty() {
|
|
||||||
let mut arr: Array = vec![];
|
|
||||||
let popped = arr.pop();
|
|
||||||
assert!(popped.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_shift() {
|
|
||||||
let mut arr: Array = vec![Dynamic::from(1), Dynamic::from(2), Dynamic::from(3)];
|
|
||||||
let shifted = arr.remove(0);
|
|
||||||
assert_eq!(arr.len(), 2);
|
|
||||||
assert_eq!(shifted.as_int().unwrap_or(0), 1);
|
|
||||||
assert_eq!(arr[0].as_int().unwrap_or(0), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unshift() {
|
|
||||||
let mut arr: Array = vec![Dynamic::from(2), Dynamic::from(3)];
|
|
||||||
arr.insert(0, Dynamic::from(1));
|
|
||||||
assert_eq!(arr.len(), 3);
|
|
||||||
assert_eq!(arr[0].as_int().unwrap_or(0), 1);
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn make_test_array() -> Array {
|
|
||||||
vec![
|
|
||||||
Dynamic::from(1),
|
|
||||||
Dynamic::from(2),
|
|
||||||
Dynamic::from(3),
|
|
||||||
Dynamic::from(4),
|
|
||||||
Dynamic::from(5),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_from_start() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, 2, None);
|
|
||||||
assert_eq!(result.len(), 3);
|
|
||||||
assert_eq!(result[0].as_int().unwrap(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_with_end() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, 1, Some(3));
|
|
||||||
assert_eq!(result.len(), 2);
|
|
||||||
assert_eq!(result[0].as_int().unwrap(), 2);
|
|
||||||
assert_eq!(result[1].as_int().unwrap(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_negative_start() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, -2, None);
|
|
||||||
assert_eq!(result.len(), 2);
|
|
||||||
assert_eq!(result[0].as_int().unwrap(), 4);
|
|
||||||
assert_eq!(result[1].as_int().unwrap(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_negative_end() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, 0, Some(-2));
|
|
||||||
assert_eq!(result.len(), 3);
|
|
||||||
assert_eq!(result[0].as_int().unwrap(), 1);
|
|
||||||
assert_eq!(result[2].as_int().unwrap(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_out_of_bounds() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, 10, None);
|
|
||||||
assert!(result.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slice_empty_range() {
|
|
||||||
let arr = make_test_array();
|
|
||||||
let result = slice_array(&arr, 3, Some(2));
|
|
||||||
assert!(result.is_empty());
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sort_integers() {
|
|
||||||
let arr: Array = vec![
|
|
||||||
Dynamic::from(3),
|
|
||||||
Dynamic::from(1),
|
|
||||||
Dynamic::from(4),
|
|
||||||
Dynamic::from(1),
|
|
||||||
Dynamic::from(5),
|
|
||||||
];
|
|
||||||
let sorted = sort_array(arr, false);
|
|
||||||
assert_eq!(sorted[0].as_int().unwrap(), 1);
|
|
||||||
assert_eq!(sorted[1].as_int().unwrap(), 1);
|
|
||||||
assert_eq!(sorted[2].as_int().unwrap(), 3);
|
|
||||||
assert_eq!(sorted[3].as_int().unwrap(), 4);
|
|
||||||
assert_eq!(sorted[4].as_int().unwrap(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sort_strings() {
|
|
||||||
let arr: Array = vec![
|
|
||||||
Dynamic::from("banana"),
|
|
||||||
Dynamic::from("apple"),
|
|
||||||
Dynamic::from("cherry"),
|
|
||||||
];
|
|
||||||
let sorted = sort_array(arr, false);
|
|
||||||
assert_eq!(sorted[0].clone().into_string().unwrap(), "apple");
|
|
||||||
assert_eq!(sorted[1].clone().into_string().unwrap(), "banana");
|
|
||||||
assert_eq!(sorted[2].clone().into_string().unwrap(), "cherry");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sort_descending() {
|
|
||||||
let arr: Array = vec![Dynamic::from(1), Dynamic::from(3), Dynamic::from(2)];
|
|
||||||
let sorted = sort_array(arr, true);
|
|
||||||
assert_eq!(sorted[0].as_int().unwrap(), 3);
|
|
||||||
assert_eq!(sorted[1].as_int().unwrap(), 2);
|
|
||||||
assert_eq!(sorted[2].as_int().unwrap(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_compare_dynamic_numbers() {
|
|
||||||
let a = Dynamic::from(5);
|
|
||||||
let b = Dynamic::from(3);
|
|
||||||
assert_eq!(compare_dynamic(&a, &b), std::cmp::Ordering::Greater);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_compare_dynamic_strings() {
|
|
||||||
let a = Dynamic::from("apple");
|
|
||||||
let b = Dynamic::from("banana");
|
|
||||||
assert_eq!(compare_dynamic(&a, &b), std::cmp::Ordering::Less);
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_integers() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
arr.push(Dynamic::from(2_i64));
|
|
||||||
arr.push(Dynamic::from(2_i64));
|
|
||||||
arr.push(Dynamic::from(3_i64));
|
|
||||||
arr.push(Dynamic::from(3_i64));
|
|
||||||
arr.push(Dynamic::from(3_i64));
|
|
||||||
arr.push(Dynamic::from(4_i64));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert_eq!(result.len(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_strings() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from("Alice"));
|
|
||||||
arr.push(Dynamic::from("Bob"));
|
|
||||||
arr.push(Dynamic::from("Alice"));
|
|
||||||
arr.push(Dynamic::from("Charlie"));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert_eq!(result.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_preserves_order() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from("C"));
|
|
||||||
arr.push(Dynamic::from("A"));
|
|
||||||
arr.push(Dynamic::from("B"));
|
|
||||||
arr.push(Dynamic::from("A"));
|
|
||||||
arr.push(Dynamic::from("C"));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert_eq!(result.len(), 3);
|
|
||||||
assert_eq!(result[0].to_string(), "C");
|
|
||||||
assert_eq!(result[1].to_string(), "A");
|
|
||||||
assert_eq!(result[2].to_string(), "B");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_empty_array() {
|
|
||||||
let arr = Array::new();
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert!(result.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_single_element() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from(42_i64));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_all_same() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
assert_eq!(result.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unique_mixed_types() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
arr.push(Dynamic::from("1"));
|
|
||||||
arr.push(Dynamic::from(1_i64));
|
|
||||||
|
|
||||||
let result = unique_array(arr);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(result.len() >= 1 && result.len() <= 2);
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_time_string() {
|
|
||||||
let result = parse_time_string("2024-01-15 14:30");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_time_string("tomorrow at 3pm");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_time_string("in 2 hours");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_date_string() {
|
|
||||||
let result = parse_date_string("today");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_date_string("2024-01-15");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_date_string("tomorrow");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_hour() {
|
|
||||||
assert_eq!(extract_hour_from_string("3pm"), Some(15));
|
|
||||||
assert_eq!(extract_hour_from_string("3 PM"), Some(15));
|
|
||||||
assert_eq!(extract_hour_from_string("10am"), Some(10));
|
|
||||||
assert_eq!(extract_hour_from_string("12am"), Some(0));
|
|
||||||
assert_eq!(extract_hour_from_string("12pm"), Some(12));
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_card_style_from_string() {
|
|
||||||
assert!(matches!(CardStyle::from("minimal"), CardStyle::Minimal));
|
|
||||||
assert!(matches!(CardStyle::from("VIBRANT"), CardStyle::Vibrant));
|
|
||||||
assert!(matches!(CardStyle::from("dark"), CardStyle::Dark));
|
|
||||||
assert!(matches!(CardStyle::from("unknown"), CardStyle::Modern));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_card_dimensions_for_style() {
|
|
||||||
let story_dims = CardDimensions::for_style(&CardStyle::Story);
|
|
||||||
assert_eq!(story_dims.width, 1080);
|
|
||||||
assert_eq!(story_dims.height, 1920);
|
|
||||||
|
|
||||||
let square_dims = CardDimensions::for_style(&CardStyle::Modern);
|
|
||||||
assert_eq!(square_dims.width, 1080);
|
|
||||||
assert_eq!(square_dims.height, 1080);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_card_config_default() {
|
|
||||||
let config = CardConfig::default();
|
|
||||||
assert!(matches!(config.style, CardStyle::Modern));
|
|
||||||
assert!(config.include_hashtags);
|
|
||||||
assert!(config.include_caption);
|
|
||||||
assert!(config.brand_watermark.is_none());
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_clear_kb_syntax() {
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
|
|
||||||
assert!(engine
|
|
||||||
.register_custom_syntax(&["CLEAR_KB", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
|
|
||||||
assert!(engine
|
|
||||||
.register_custom_syntax(&["CLEAR_KB"], true, |_, _| Ok(Dynamic::UNIT))
|
|
||||||
.is_ok());
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
use botserver::basic::keywords::code_sandbox::{
|
|
||||||
generate_node_lxc_config, generate_python_lxc_config, CodeLanguage, ExecutionResult,
|
|
||||||
SandboxConfig, SandboxRuntime,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sandbox_config_default() {
|
|
||||||
let config = SandboxConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.timeout_seconds, 30);
|
|
||||||
assert_eq!(config.memory_limit_mb, 256);
|
|
||||||
assert!(!config.network_enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_execution_result_success() {
|
|
||||||
let result = ExecutionResult::success("Hello, World!".to_string(), String::new(), 100);
|
|
||||||
assert!(result.is_success());
|
|
||||||
assert_eq!(result.output(), "Hello, World!");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_execution_result_error() {
|
|
||||||
let result = ExecutionResult::error("Something went wrong");
|
|
||||||
assert!(!result.is_success());
|
|
||||||
assert!(result.output().contains("Error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_execution_result_timeout() {
|
|
||||||
let result = ExecutionResult::timeout();
|
|
||||||
assert!(!result.is_success());
|
|
||||||
assert!(result.timed_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_code_language_from_str() {
|
|
||||||
assert_eq!(CodeLanguage::from("python"), CodeLanguage::Python);
|
|
||||||
assert_eq!(CodeLanguage::from("PYTHON"), CodeLanguage::Python);
|
|
||||||
assert_eq!(CodeLanguage::from("py"), CodeLanguage::Python);
|
|
||||||
assert_eq!(CodeLanguage::from("javascript"), CodeLanguage::JavaScript);
|
|
||||||
assert_eq!(CodeLanguage::from("js"), CodeLanguage::JavaScript);
|
|
||||||
assert_eq!(CodeLanguage::from("node"), CodeLanguage::JavaScript);
|
|
||||||
assert_eq!(CodeLanguage::from("bash"), CodeLanguage::Bash);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_code_language_file_extension() {
|
|
||||||
assert_eq!(CodeLanguage::Python.file_extension(), "py");
|
|
||||||
assert_eq!(CodeLanguage::JavaScript.file_extension(), "js");
|
|
||||||
assert_eq!(CodeLanguage::Bash.file_extension(), "sh");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_code_language_interpreter() {
|
|
||||||
assert_eq!(CodeLanguage::Python.interpreter(), "python3");
|
|
||||||
assert_eq!(CodeLanguage::JavaScript.interpreter(), "node");
|
|
||||||
assert_eq!(CodeLanguage::Bash.interpreter(), "bash");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sandbox_runtime_from_str() {
|
|
||||||
assert_eq!(SandboxRuntime::from("lxc"), SandboxRuntime::LXC);
|
|
||||||
assert_eq!(SandboxRuntime::from("docker"), SandboxRuntime::Docker);
|
|
||||||
assert_eq!(
|
|
||||||
SandboxRuntime::from("firecracker"),
|
|
||||||
SandboxRuntime::Firecracker
|
|
||||||
);
|
|
||||||
assert_eq!(SandboxRuntime::from("unknown"), SandboxRuntime::Process);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lxc_config_generation() {
|
|
||||||
let python_config = generate_python_lxc_config();
|
|
||||||
assert!(python_config.contains("gb-sandbox-python"));
|
|
||||||
assert!(python_config.contains("memory.max"));
|
|
||||||
|
|
||||||
let node_config = generate_node_lxc_config();
|
|
||||||
assert!(node_config.contains("gb-sandbox-node"));
|
|
||||||
assert!(node_config.contains("/usr/bin/node"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_module_structure() {
|
|
||||||
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_due_date() {
|
|
||||||
assert!(parse_due_date("tomorrow").is_ok());
|
|
||||||
assert!(parse_due_date("+3 days").is_ok());
|
|
||||||
assert!(parse_due_date("2024-12-31").is_ok());
|
|
||||||
assert!(parse_due_date("null").unwrap().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_determine_priority() {
|
|
||||||
let tomorrow = Some(Utc::now() + Duration::days(1));
|
|
||||||
assert_eq!(determine_priority(tomorrow), "high");
|
|
||||||
|
|
||||||
let next_week = Some(Utc::now() + Duration::days(7));
|
|
||||||
assert_eq!(determine_priority(next_week), "medium");
|
|
||||||
|
|
||||||
let next_month = Some(Utc::now() + Duration::days(30));
|
|
||||||
assert_eq!(determine_priority(next_month), "low");
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use botserver::basic::keywords::crm::attendance::create_fallback_tips;
|
|
||||||
use rhai::Map;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fallback_tips_urgent() {
|
|
||||||
let tips = create_fallback_tips("This is URGENT! Help now!");
|
|
||||||
let result = tips.try_cast::<Map>().unwrap();
|
|
||||||
assert!(result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fallback_tips_question() {
|
|
||||||
let tips = create_fallback_tips("Can you help me with this?");
|
|
||||||
let result = tips.try_cast::<Map>().unwrap();
|
|
||||||
assert!(result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_polish_message() {
|
|
||||||
let polished = polish_text("thx 4 ur msg", "professional");
|
|
||||||
assert!(!polished.contains("thx"));
|
|
||||||
assert!(polished.contains("your"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_polish_message_capitalization() {
|
|
||||||
let polished = polish_text("hello there", "professional");
|
|
||||||
assert!(polished.starts_with('H'));
|
|
||||||
assert!(polished.ends_with('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn polish_text(message: &str, _tone: &str) -> String {
|
|
||||||
let mut polished = message.to_string();
|
|
||||||
polished = polished
|
|
||||||
.replace("thx", "Thank you")
|
|
||||||
.replace("u ", "you ")
|
|
||||||
.replace(" u", " you")
|
|
||||||
.replace("ur ", "your ")
|
|
||||||
.replace("ill ", "I'll ")
|
|
||||||
.replace("dont ", "don't ")
|
|
||||||
.replace("cant ", "can't ")
|
|
||||||
.replace("wont ", "won't ")
|
|
||||||
.replace("im ", "I'm ")
|
|
||||||
.replace("ive ", "I've ");
|
|
||||||
if let Some(first_char) = polished.chars().next() {
|
|
||||||
polished = first_char.to_uppercase().to_string() + &polished[1..];
|
|
||||||
}
|
|
||||||
if !polished.ends_with('.') && !polished.ends_with('!') && !polished.ends_with('?') {
|
|
||||||
polished.push('.');
|
|
||||||
}
|
|
||||||
polished
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sentiment_positive() {
|
|
||||||
let result = analyze_text_sentiment("Thank you so much! This is great!");
|
|
||||||
assert_eq!(result, "positive");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sentiment_negative() {
|
|
||||||
let result = analyze_text_sentiment("This is terrible! I'm so frustrated!");
|
|
||||||
assert_eq!(result, "negative");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sentiment_neutral() {
|
|
||||||
let result = analyze_text_sentiment("The meeting is at 3pm.");
|
|
||||||
assert_eq!(result, "neutral");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn analyze_text_sentiment(message: &str) -> &'static str {
|
|
||||||
let msg_lower = message.to_lowercase();
|
|
||||||
let positive_words = [
|
|
||||||
"thank", "great", "perfect", "awesome", "excellent", "good", "happy", "love",
|
|
||||||
];
|
|
||||||
let negative_words = [
|
|
||||||
"angry", "frustrated", "terrible", "awful", "horrible", "hate", "disappointed", "problem",
|
|
||||||
"issue",
|
|
||||||
];
|
|
||||||
let positive_count = positive_words
|
|
||||||
.iter()
|
|
||||||
.filter(|w| msg_lower.contains(*w))
|
|
||||||
.count();
|
|
||||||
let negative_count = negative_words
|
|
||||||
.iter()
|
|
||||||
.filter(|w| msg_lower.contains(*w))
|
|
||||||
.count();
|
|
||||||
if positive_count > negative_count {
|
|
||||||
"positive"
|
|
||||||
} else if negative_count > positive_count {
|
|
||||||
"negative"
|
|
||||||
} else {
|
|
||||||
"neutral"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_smart_replies_count() {
|
|
||||||
let replies = generate_smart_replies();
|
|
||||||
assert_eq!(replies.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_smart_replies_content() {
|
|
||||||
let replies = generate_smart_replies();
|
|
||||||
assert!(replies.iter().any(|r| r.contains("Thank you")));
|
|
||||||
assert!(replies.iter().any(|r| r.contains("understand")));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_smart_replies() -> Vec<String> {
|
|
||||||
vec![
|
|
||||||
"Thank you for reaching out! I'd be happy to help you with that.".to_string(),
|
|
||||||
"I understand your concern. Let me look into this for you right away.".to_string(),
|
|
||||||
"Is there anything else I can help you with today?".to_string(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_calculate_lead_score_empty() {
|
|
||||||
let lead_data = Map::new();
|
|
||||||
let score = calculate_lead_score(&lead_data, None);
|
|
||||||
assert_eq!(score, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_calculate_lead_score_basic() {
|
|
||||||
let mut lead_data = Map::new();
|
|
||||||
lead_data.insert("job_title".into(), Dynamic::from("CEO"));
|
|
||||||
lead_data.insert("company_size".into(), Dynamic::from(500_i64));
|
|
||||||
lead_data.insert("email".into(), Dynamic::from("ceo@company.com"));
|
|
||||||
|
|
||||||
let score = calculate_lead_score(&lead_data, None);
|
|
||||||
assert!(score > 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_calculate_lead_score_with_title() {
|
|
||||||
let mut lead_data = Map::new();
|
|
||||||
lead_data.insert("job_title".into(), Dynamic::from("CTO"));
|
|
||||||
|
|
||||||
let score = calculate_lead_score(&lead_data, None);
|
|
||||||
assert!(score >= 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_determine_priority() {
|
|
||||||
assert_eq!(determine_priority(95), "CRITICAL");
|
|
||||||
assert_eq!(determine_priority(75), "HIGH");
|
|
||||||
assert_eq!(determine_priority(55), "MEDIUM");
|
|
||||||
assert_eq!(determine_priority(35), "LOW");
|
|
||||||
assert_eq!(determine_priority(10), "MINIMAL");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_score_clamping() {
|
|
||||||
let mut lead_data = Map::new();
|
|
||||||
lead_data.insert("budget".into(), Dynamic::from(1000000_i64));
|
|
||||||
|
|
||||||
let score = calculate_lead_score(&lead_data, None);
|
|
||||||
assert!(
|
|
||||||
score <= 100,
|
|
||||||
"Score should be clamped to 100, got {}",
|
|
||||||
score
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sanitize_identifier() {
|
|
||||||
assert_eq!(sanitize_identifier("users"), "users");
|
|
||||||
assert_eq!(sanitize_identifier("user_name"), "user_name");
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_identifier("users; DROP TABLE users;"),
|
|
||||||
"usersDROPTABLEusers"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sanitize_sql() {
|
|
||||||
assert_eq!(sanitize_sql("hello"), "hello");
|
|
||||||
assert_eq!(sanitize_sql("it's"), "it''s");
|
|
||||||
assert_eq!(sanitize_sql("O'Brien"), "O''Brien");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_condition() {
|
|
||||||
let (field, op, value) = parse_condition_internal("status=active").unwrap();
|
|
||||||
assert_eq!(field, "status");
|
|
||||||
assert_eq!(op, "=");
|
|
||||||
assert_eq!(value, "active");
|
|
||||||
|
|
||||||
let (field, op, value) = parse_condition_internal("age>=18").unwrap();
|
|
||||||
assert_eq!(field, "age");
|
|
||||||
assert_eq!(op, ">=");
|
|
||||||
assert_eq!(value, "18");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_filter_clause() {
|
|
||||||
let clause = parse_filter_clause("name=John").unwrap();
|
|
||||||
assert!(clause.contains("name"));
|
|
||||||
assert!(clause.contains("John"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dateadd_days() {
|
|
||||||
assert_eq!(dateadd_impl("2025-01-15", 5, "day"), "2025-01-20");
|
|
||||||
assert_eq!(dateadd_impl("2025-01-15", -10, "day"), "2025-01-05");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dateadd_months() {
|
|
||||||
assert_eq!(dateadd_impl("2025-01-15", 1, "month"), "2025-02-15");
|
|
||||||
assert_eq!(dateadd_impl("2025-01-15", -1, "month"), "2024-12-15");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dateadd_years() {
|
|
||||||
assert_eq!(dateadd_impl("2025-01-15", 1, "year"), "2026-01-15");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_datediff_days() {
|
|
||||||
assert_eq!(datediff_impl("2025-01-01", "2025-01-15", "day"), 14);
|
|
||||||
assert_eq!(datediff_impl("2025-01-15", "2025-01-01", "day"), -14);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_datediff_months() {
|
|
||||||
assert_eq!(datediff_impl("2025-01-01", "2025-03-01", "month"), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_datediff_years() {
|
|
||||||
assert_eq!(datediff_impl("2024-01-01", "2025-01-01", "year"), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_date() {
|
|
||||||
assert!(parse_date("2025-01-15").is_some());
|
|
||||||
assert!(parse_date("15/01/2025").is_some());
|
|
||||||
assert!(parse_date("invalid").is_none());
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_date() {
|
|
||||||
let date = parse_date("2025-01-22");
|
|
||||||
assert!(date.is_some());
|
|
||||||
let d = date.unwrap();
|
|
||||||
assert_eq!(d.year(), 2025);
|
|
||||||
assert_eq!(d.month(), 1);
|
|
||||||
assert_eq!(d.day(), 22);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_datetime() {
|
|
||||||
let dt = parse_datetime("2025-01-22 14:30:45");
|
|
||||||
assert!(dt.is_some());
|
|
||||||
let d = dt.unwrap();
|
|
||||||
assert_eq!(d.hour(), 14);
|
|
||||||
assert_eq!(d.minute(), 30);
|
|
||||||
assert_eq!(d.second(), 45);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_invalid_date() {
|
|
||||||
let date = parse_date("invalid");
|
|
||||||
assert!(date.is_none());
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_create_datetime_map() {
|
|
||||||
let now = Local::now();
|
|
||||||
let map = create_datetime_map(now);
|
|
||||||
|
|
||||||
assert!(map.contains_key("year"));
|
|
||||||
assert!(map.contains_key("month"));
|
|
||||||
assert!(map.contains_key("day"));
|
|
||||||
assert!(map.contains_key("hour"));
|
|
||||||
assert!(map.contains_key("minute"));
|
|
||||||
assert!(map.contains_key("second"));
|
|
||||||
assert!(map.contains_key("weekday"));
|
|
||||||
assert!(map.contains_key("timestamp"));
|
|
||||||
assert!(map.contains_key("formatted"));
|
|
||||||
assert!(map.contains_key("is_weekend"));
|
|
||||||
assert!(map.contains_key("quarter"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_year_extraction() {
|
|
||||||
let now = Local::now();
|
|
||||||
let map = create_datetime_map(now);
|
|
||||||
|
|
||||||
let year = map.get("year").unwrap().as_int().unwrap();
|
|
||||||
assert!(year >= 2024);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_month_range() {
|
|
||||||
let now = Local::now();
|
|
||||||
let map = create_datetime_map(now);
|
|
||||||
|
|
||||||
let month = map.get("month").unwrap().as_int().unwrap();
|
|
||||||
assert!(month >= 1 && month <= 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_hour12_range() {
|
|
||||||
let now = Local::now();
|
|
||||||
let map = create_datetime_map(now);
|
|
||||||
|
|
||||||
let hour12 = map.get("hour12").unwrap().as_int().unwrap();
|
|
||||||
assert!(hour12 >= 1 && hour12 <= 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_quarter_calculation() {
|
|
||||||
let now = Local::now();
|
|
||||||
let map = create_datetime_map(now);
|
|
||||||
|
|
||||||
let quarter = map.get("quarter").unwrap().as_int().unwrap();
|
|
||||||
assert!(quarter >= 1 && quarter <= 4);
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
use botserver::basic::keywords::episodic_memory::{
|
|
||||||
extract_json, ConversationMessage, Episode, EpisodicMemoryConfig, EpisodicMemoryManager,
|
|
||||||
ResolutionStatus, Sentiment,
|
|
||||||
};
|
|
||||||
use chrono::Utc;
|
|
||||||
use rhai::Map;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = EpisodicMemoryConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.threshold, 4);
|
|
||||||
assert_eq!(config.history, 2);
|
|
||||||
assert_eq!(config.max_episodes, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_should_summarize() {
|
|
||||||
let manager = EpisodicMemoryManager::new(EpisodicMemoryConfig {
|
|
||||||
enabled: true,
|
|
||||||
threshold: 4,
|
|
||||||
history: 2,
|
|
||||||
auto_summarize: true,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(!manager.should_summarize(2));
|
|
||||||
assert!(manager.should_summarize(4));
|
|
||||||
assert!(manager.should_summarize(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_json() {
|
|
||||||
let response = "Here's the summary:\n```json\n{\"summary\": \"test\"}\n```\n";
|
|
||||||
assert!(extract_json(response).is_ok());
|
|
||||||
|
|
||||||
let response = "The result is {\"summary\": \"test\"}";
|
|
||||||
assert!(extract_json(response).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_generate_summary_prompt() {
|
|
||||||
let manager = EpisodicMemoryManager::new(EpisodicMemoryConfig::default());
|
|
||||||
let messages = vec![ConversationMessage {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
role: "user".to_string(),
|
|
||||||
content: "Hello".to_string(),
|
|
||||||
timestamp: Utc::now(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let prompt = manager.generate_summary_prompt(&messages);
|
|
||||||
assert!(prompt.contains("CONVERSATION:"));
|
|
||||||
assert!(prompt.contains("Hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_summary_response() {
|
|
||||||
let manager = EpisodicMemoryManager::new(EpisodicMemoryConfig::default());
|
|
||||||
let response = r#"{
|
|
||||||
"summary": "User asked about billing",
|
|
||||||
"key_topics": ["billing", "payment"],
|
|
||||||
"decisions": [],
|
|
||||||
"action_items": [],
|
|
||||||
"sentiment": {"score": 0.5, "label": "positive", "confidence": 0.8},
|
|
||||||
"resolution": "resolved"
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let messages = vec![ConversationMessage {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
role: "user".to_string(),
|
|
||||||
content: "What's my balance?".to_string(),
|
|
||||||
timestamp: Utc::now(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let episode = manager.parse_summary_response(
|
|
||||||
response,
|
|
||||||
&messages,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(episode.is_ok());
|
|
||||||
let ep = episode.unwrap();
|
|
||||||
assert_eq!(ep.summary, "User asked about billing");
|
|
||||||
assert_eq!(ep.key_topics, vec!["billing", "payment"]);
|
|
||||||
assert_eq!(ep.resolution, ResolutionStatus::Resolved);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_episode_to_dynamic() {
|
|
||||||
let episode = Episode {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
user_id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
session_id: Uuid::new_v4(),
|
|
||||||
summary: "Test summary".to_string(),
|
|
||||||
key_topics: vec!["topic1".to_string()],
|
|
||||||
decisions: vec![],
|
|
||||||
action_items: vec![],
|
|
||||||
sentiment: Sentiment::default(),
|
|
||||||
resolution: ResolutionStatus::Resolved,
|
|
||||||
message_count: 5,
|
|
||||||
message_ids: vec![],
|
|
||||||
created_at: Utc::now(),
|
|
||||||
conversation_start: Utc::now(),
|
|
||||||
conversation_end: Utc::now(),
|
|
||||||
metadata: serde_json::json!({}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dynamic = episode.to_dynamic();
|
|
||||||
assert!(dynamic.is::<Map>());
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_error_map() {
|
|
||||||
use rhai::{Dynamic, Map};
|
|
||||||
|
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert("error".into(), Dynamic::from(true));
|
|
||||||
map.insert("message".into(), Dynamic::from("test error"));
|
|
||||||
|
|
||||||
assert!(map.contains_key("error"));
|
|
||||||
assert_eq!(map.get("error").unwrap().as_bool().unwrap_or(false), true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_error_resume_next_flag() {
|
|
||||||
|
|
||||||
assert!(!is_error_resume_next_active());
|
|
||||||
|
|
||||||
|
|
||||||
set_error_resume_next(true);
|
|
||||||
assert!(is_error_resume_next_active());
|
|
||||||
|
|
||||||
|
|
||||||
set_error_resume_next(false);
|
|
||||||
assert!(!is_error_resume_next_active());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_error_storage() {
|
|
||||||
clear_last_error();
|
|
||||||
assert!(get_last_error().is_none());
|
|
||||||
assert_eq!(get_error_number(), 0);
|
|
||||||
|
|
||||||
set_last_error("Test error", 42);
|
|
||||||
assert_eq!(get_last_error(), Some("Test error".to_string()));
|
|
||||||
assert_eq!(get_error_number(), 42);
|
|
||||||
|
|
||||||
clear_last_error();
|
|
||||||
assert!(get_last_error().is_none());
|
|
||||||
assert_eq!(get_error_number(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_handle_error_without_resume_next() {
|
|
||||||
set_error_resume_next(false);
|
|
||||||
clear_last_error();
|
|
||||||
|
|
||||||
let result: Result<String, Box<dyn std::error::Error + Send + Sync>> =
|
|
||||||
Err("Test error".into());
|
|
||||||
let handled = handle_error(result);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(handled.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_handle_error_with_resume_next() {
|
|
||||||
set_error_resume_next(true);
|
|
||||||
clear_last_error();
|
|
||||||
|
|
||||||
let result: Result<String, Box<dyn std::error::Error + Send + Sync>> =
|
|
||||||
Err("Test error".into());
|
|
||||||
let handled = handle_error(result);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(handled.is_ok());
|
|
||||||
assert_eq!(get_last_error(), Some("Test error".to_string()));
|
|
||||||
|
|
||||||
|
|
||||||
set_error_resume_next(false);
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_placeholder() {
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dynamic_to_json() {
|
|
||||||
let dynamic = Dynamic::from("hello");
|
|
||||||
let json = dynamic_to_json(&dynamic);
|
|
||||||
assert_eq!(json, Value::String("hello".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dynamic_to_file_data() {
|
|
||||||
let dynamic = Dynamic::from("test content");
|
|
||||||
let file_data = dynamic_to_file_data(&dynamic);
|
|
||||||
assert_eq!(file_data.filename, "file");
|
|
||||||
assert!(!file_data.content.is_empty());
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_email() {
|
|
||||||
assert!(validate_email("test@example.com").is_valid);
|
|
||||||
assert!(validate_email("user.name+tag@domain.co.uk").is_valid);
|
|
||||||
assert!(!validate_email("invalid").is_valid);
|
|
||||||
assert!(!validate_email("@nodomain.com").is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_date() {
|
|
||||||
assert!(validate_date("25/12/2024").is_valid);
|
|
||||||
assert!(validate_date("2024-12-25").is_valid);
|
|
||||||
assert!(validate_date("today").is_valid);
|
|
||||||
assert!(validate_date("tomorrow").is_valid);
|
|
||||||
assert!(!validate_date("invalid").is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_cpf() {
|
|
||||||
assert!(validate_cpf("529.982.247-25").is_valid);
|
|
||||||
assert!(validate_cpf("52998224725").is_valid);
|
|
||||||
assert!(!validate_cpf("111.111.111-11").is_valid);
|
|
||||||
assert!(!validate_cpf("123").is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_money() {
|
|
||||||
let result = validate_money("R$ 1.234,56");
|
|
||||||
assert!(result.is_valid);
|
|
||||||
assert_eq!(result.normalized_value, "1234.56");
|
|
||||||
|
|
||||||
let result = validate_money("$1,234.56");
|
|
||||||
assert!(result.is_valid);
|
|
||||||
assert_eq!(result.normalized_value, "1234.56");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_boolean() {
|
|
||||||
assert!(validate_boolean("yes").is_valid);
|
|
||||||
assert!(validate_boolean("sim").is_valid);
|
|
||||||
assert!(validate_boolean("no").is_valid);
|
|
||||||
assert!(validate_boolean("não").is_valid);
|
|
||||||
assert!(!validate_boolean("maybe").is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_menu() {
|
|
||||||
let options = vec![
|
|
||||||
"Apple".to_string(),
|
|
||||||
"Banana".to_string(),
|
|
||||||
"Cherry".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
assert!(validate_menu("Apple", &options).is_valid);
|
|
||||||
assert!(validate_menu("1", &options).is_valid);
|
|
||||||
assert!(validate_menu("ban", &options).is_valid);
|
|
||||||
assert!(!validate_menu("Orange", &options).is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_validate_credit_card() {
|
|
||||||
|
|
||||||
assert!(validate_credit_card("4111 1111 1111 1111").is_valid);
|
|
||||||
|
|
||||||
assert!(!validate_credit_card("1234567890123456").is_valid);
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dynamic_to_json_string() {
|
|
||||||
let dynamic = Dynamic::from("hello");
|
|
||||||
let json = dynamic_to_json(&dynamic);
|
|
||||||
assert_eq!(json, Value::String("hello".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_dynamic_to_json_number() {
|
|
||||||
let dynamic = Dynamic::from(42_i64);
|
|
||||||
let json = dynamic_to_json(&dynamic);
|
|
||||||
assert_eq!(json, Value::Number(42.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_build_soap_envelope() {
|
|
||||||
let params = json!({"name": "John", "age": 30});
|
|
||||||
let envelope = build_soap_envelope("GetUser", ¶ms);
|
|
||||||
assert!(envelope.contains("<GetUser"));
|
|
||||||
assert!(envelope.contains("<name>John</name>"));
|
|
||||||
assert!(envelope.contains("<age>30</age>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_soap_response() {
|
|
||||||
let xml = r#"<?xml version="1.0"?><soap:Envelope><soap:Body><Result>Success</Result></soap:Body></soap:Envelope>"#;
|
|
||||||
let result = parse_soap_response(xml);
|
|
||||||
assert!(result.get("raw").is_some());
|
|
||||||
}
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
use botserver::basic::keywords::human_approval::{
|
|
||||||
ApprovalChannel, ApprovalConfig, ApprovalDecision, ApprovalManager, ApprovalStatus,
|
|
||||||
};
|
|
||||||
use chrono::{Duration, Utc};
|
|
||||||
use rhai::Map;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = ApprovalConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.default_timeout, 3600);
|
|
||||||
assert_eq!(config.max_reminders, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_request() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let request = manager.create_request(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
"expense_approval",
|
|
||||||
ApprovalChannel::Email,
|
|
||||||
"manager@example.com",
|
|
||||||
serde_json::json!({"amount": 1000}),
|
|
||||||
"Please approve expense",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(request.status, ApprovalStatus::Pending);
|
|
||||||
assert_eq!(request.approval_type, "expense_approval");
|
|
||||||
assert!(request.expires_at > Utc::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_expired() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let mut request = manager.create_request(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
"test",
|
|
||||||
ApprovalChannel::Email,
|
|
||||||
"test@example.com",
|
|
||||||
serde_json::json!({}),
|
|
||||||
"Test",
|
|
||||||
Some(1),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!manager.is_expired(&request));
|
|
||||||
|
|
||||||
request.expires_at = Utc::now() - Duration::seconds(10);
|
|
||||||
assert!(manager.is_expired(&request));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_process_decision() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let mut request = manager.create_request(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
"test",
|
|
||||||
ApprovalChannel::Email,
|
|
||||||
"test@example.com",
|
|
||||||
serde_json::json!({}),
|
|
||||||
"Test",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
manager.process_decision(
|
|
||||||
&mut request,
|
|
||||||
ApprovalDecision::Approve,
|
|
||||||
"manager@example.com",
|
|
||||||
Some("Looks good!".to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(request.status, ApprovalStatus::Approved);
|
|
||||||
assert_eq!(request.decision, Some(ApprovalDecision::Approve));
|
|
||||||
assert_eq!(request.decided_by, Some("manager@example.com".to_string()));
|
|
||||||
assert_eq!(request.comments, Some("Looks good!".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_evaluate_condition() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let context = serde_json::json!({
|
|
||||||
"amount": 15000,
|
|
||||||
"priority": 2
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(manager
|
|
||||||
.evaluate_condition("amount > 10000", &context)
|
|
||||||
.unwrap());
|
|
||||||
assert!(!manager
|
|
||||||
.evaluate_condition("amount > 20000", &context)
|
|
||||||
.unwrap());
|
|
||||||
assert!(manager
|
|
||||||
.evaluate_condition("priority == 2", &context)
|
|
||||||
.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_handle_timeout_with_default() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let mut request = manager.create_request(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
"test",
|
|
||||||
ApprovalChannel::Email,
|
|
||||||
"test@example.com",
|
|
||||||
serde_json::json!({}),
|
|
||||||
"Test",
|
|
||||||
None,
|
|
||||||
Some(ApprovalDecision::Approve),
|
|
||||||
);
|
|
||||||
|
|
||||||
manager.handle_timeout(&mut request);
|
|
||||||
|
|
||||||
assert_eq!(request.status, ApprovalStatus::Approved);
|
|
||||||
assert_eq!(request.decision, Some(ApprovalDecision::Approve));
|
|
||||||
assert_eq!(request.decided_by, Some("system:timeout".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_request_to_dynamic() {
|
|
||||||
let manager = ApprovalManager::new(ApprovalConfig::default());
|
|
||||||
let request = manager.create_request(
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
Uuid::new_v4(),
|
|
||||||
"test",
|
|
||||||
ApprovalChannel::Email,
|
|
||||||
"test@example.com",
|
|
||||||
serde_json::json!({"key": "value"}),
|
|
||||||
"Test message",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let dynamic = request.to_dynamic();
|
|
||||||
assert!(dynamic.is::<Map>());
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_csv_line_simple() {
|
|
||||||
let line = "a,b,c";
|
|
||||||
let result = parse_csv_line(line);
|
|
||||||
assert_eq!(result, vec!["a", "b", "c"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_csv_line_quoted() {
|
|
||||||
let line = r#""hello, world",test,"another, value""#;
|
|
||||||
let result = parse_csv_line(line);
|
|
||||||
assert_eq!(result, vec!["hello, world", "test", "another, value"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_escape_csv_value() {
|
|
||||||
assert_eq!(escape_csv_value("simple"), "simple");
|
|
||||||
assert_eq!(escape_csv_value("with,comma"), "\"with,comma\"");
|
|
||||||
assert_eq!(escape_csv_value("with\"quote"), "\"with\"\"quote\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_json_to_dynamic_and_back() {
|
|
||||||
let json = serde_json::json!({
|
|
||||||
"name": "test",
|
|
||||||
"value": 42,
|
|
||||||
"active": true
|
|
||||||
});
|
|
||||||
|
|
||||||
let dynamic = json_to_dynamic(&json);
|
|
||||||
let back = dynamic_to_json(&dynamic);
|
|
||||||
|
|
||||||
assert_eq!(json, back);
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_collection_stats_serialization() {
|
|
||||||
let stats = CollectionStats {
|
|
||||||
name: "test_collection".to_string(),
|
|
||||||
vectors_count: 1000,
|
|
||||||
points_count: 1000,
|
|
||||||
segments_count: 2,
|
|
||||||
disk_data_size: 1024 * 1024,
|
|
||||||
ram_data_size: 512 * 1024,
|
|
||||||
indexed_vectors_count: 1000,
|
|
||||||
status: "green".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let json = serde_json::to_string(&stats).unwrap();
|
|
||||||
assert!(json.contains("test_collection"));
|
|
||||||
assert!(json.contains("1000"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_kb_statistics_serialization() {
|
|
||||||
let stats = KBStatistics {
|
|
||||||
total_collections: 3,
|
|
||||||
total_documents: 5000,
|
|
||||||
total_vectors: 5000,
|
|
||||||
total_disk_size_mb: 10.5,
|
|
||||||
total_ram_size_mb: 5.2,
|
|
||||||
documents_added_last_week: 100,
|
|
||||||
documents_added_last_month: 500,
|
|
||||||
collections: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let json = serde_json::to_string(&stats).unwrap();
|
|
||||||
assert!(json.contains("5000"));
|
|
||||||
assert!(json.contains("10.5"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = KnowledgeGraphConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.backend, "postgresql");
|
|
||||||
assert!(config.entity_types.contains(&"person".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extraction_prompt() {
|
|
||||||
let manager = KnowledgeGraphManager::new(KnowledgeGraphConfig::default());
|
|
||||||
let prompt = manager.generate_extraction_prompt("John works at Acme Corp.");
|
|
||||||
assert!(prompt.contains("John works at Acme Corp."));
|
|
||||||
assert!(prompt.contains("ENTITY TYPES TO EXTRACT"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_extraction_response() {
|
|
||||||
let manager = KnowledgeGraphManager::new(KnowledgeGraphConfig::default());
|
|
||||||
let response = r#"{
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"name": "John",
|
|
||||||
"canonical_name": "John Smith",
|
|
||||||
"entity_type": "person",
|
|
||||||
"confidence": 0.9,
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"relationships": [
|
|
||||||
{
|
|
||||||
"from_entity": "John",
|
|
||||||
"to_entity": "Acme Corp",
|
|
||||||
"relationship_type": "works_on",
|
|
||||||
"confidence": 0.85,
|
|
||||||
"evidence": "John works at Acme Corp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let result = manager.parse_extraction_response(response, 100, 50);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let extraction = result.unwrap();
|
|
||||||
assert_eq!(extraction.entities.len(), 1);
|
|
||||||
assert_eq!(extraction.relationships.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_entity_to_dynamic() {
|
|
||||||
let entity = KgEntity {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
entity_type: "person".to_string(),
|
|
||||||
entity_name: "John Smith".to_string(),
|
|
||||||
aliases: vec!["John".to_string()],
|
|
||||||
properties: serde_json::json!({"department": "Sales"}),
|
|
||||||
confidence: 0.95,
|
|
||||||
source: EntitySource::Manual,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dynamic = entity.to_dynamic();
|
|
||||||
assert!(dynamic.is::<Map>());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_valid_entity_type() {
|
|
||||||
let manager = KnowledgeGraphManager::new(KnowledgeGraphConfig::default());
|
|
||||||
assert!(manager.is_valid_entity_type("person"));
|
|
||||||
assert!(manager.is_valid_entity_type("PERSON"));
|
|
||||||
assert!(manager.is_valid_entity_type("organization"));
|
|
||||||
assert!(!manager.is_valid_entity_type("unknown_type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_json_to_dynamic() {
|
|
||||||
let json = serde_json::json!({
|
|
||||||
"name": "test",
|
|
||||||
"count": 42,
|
|
||||||
"active": true,
|
|
||||||
"tags": ["a", "b"]
|
|
||||||
});
|
|
||||||
|
|
||||||
let dynamic = json_to_dynamic(&json);
|
|
||||||
assert!(dynamic.is::<Map>());
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_module_structure() {
|
|
||||||
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_calculate_result_integer() {
|
|
||||||
let result = parse_calculate_result("42").unwrap();
|
|
||||||
assert_eq!(result.as_int().unwrap(), 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_calculate_result_float() {
|
|
||||||
let result = parse_calculate_result("3.14").unwrap();
|
|
||||||
assert!((result.as_float().unwrap() - 3.14).abs() < 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_calculate_result_boolean() {
|
|
||||||
let result = parse_calculate_result("true").unwrap();
|
|
||||||
assert!(result.as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_build_translate_prompt() {
|
|
||||||
let prompt = build_translate_prompt("Hello", "Spanish");
|
|
||||||
assert!(prompt.contains("Hello"));
|
|
||||||
assert!(prompt.contains("Spanish"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_abs_positive() {
|
|
||||||
assert_eq!(42_i64.abs(), 42);
|
|
||||||
assert_eq!(3.14_f64.abs(), 3.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_abs_negative() {
|
|
||||||
assert_eq!((-42_i64).abs(), 42);
|
|
||||||
assert_eq!((-3.14_f64).abs(), 3.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_abs_zero() {
|
|
||||||
assert_eq!(0_i64.abs(), 0);
|
|
||||||
assert_eq!(0.0_f64.abs(), 0.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sum() {
|
|
||||||
let arr: Vec<Dynamic> = vec![
|
|
||||||
Dynamic::from(10_i64),
|
|
||||||
Dynamic::from(20_i64),
|
|
||||||
Dynamic::from(30_i64),
|
|
||||||
];
|
|
||||||
let sum: f64 = arr
|
|
||||||
.iter()
|
|
||||||
.filter_map(|v| v.as_int().ok().map(|i| i as f64))
|
|
||||||
.sum();
|
|
||||||
assert_eq!(sum, 60.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_avg() {
|
|
||||||
let arr: Vec<f64> = vec![10.0, 20.0, 30.0];
|
|
||||||
let sum: f64 = arr.iter().sum();
|
|
||||||
let avg = sum / arr.len() as f64;
|
|
||||||
assert_eq!(avg, 20.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_empty_array() {
|
|
||||||
let arr: Vec<f64> = vec![];
|
|
||||||
let result = if arr.is_empty() { 0.0 } else { arr.iter().sum::<f64>() / arr.len() as f64 };
|
|
||||||
assert_eq!(result, 0.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_int() {
|
|
||||||
assert_eq!(3.9_f64.trunc() as i64, 3);
|
|
||||||
assert_eq!((-3.9_f64).trunc() as i64, -3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_floor_ceil() {
|
|
||||||
assert_eq!(3.7_f64.floor() as i64, 3);
|
|
||||||
assert_eq!(3.2_f64.ceil() as i64, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_minmax() {
|
|
||||||
assert_eq!(10_i64.max(5), 10);
|
|
||||||
assert_eq!(10_i64.min(5), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_mod() {
|
|
||||||
assert_eq!(17 % 5, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sgn() {
|
|
||||||
assert_eq!((-5_i64).signum(), -1);
|
|
||||||
assert_eq!(5_i64.signum(), 1);
|
|
||||||
assert_eq!(0_i64.signum(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sqrt() {
|
|
||||||
assert!((16_f64.sqrt() - 4.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_pow() {
|
|
||||||
assert!((2_f64.powf(8.0) - 256.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_max_values() {
|
|
||||||
assert_eq!(10_i64.max(5), 10);
|
|
||||||
assert_eq!(3.5_f64.max(7.2), 7.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_min_values() {
|
|
||||||
assert_eq!(10_i64.min(5), 5);
|
|
||||||
assert_eq!(3.5_f64.min(7.2), 3.5);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_mod() {
|
|
||||||
assert_eq!(17 % 5, 2);
|
|
||||||
assert_eq!(10 % 3, 1);
|
|
||||||
assert_eq!(0 % 5, 0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_round_basic() {
|
|
||||||
assert_eq!(3.7_f64.round() as i64, 4);
|
|
||||||
assert_eq!(3.2_f64.round() as i64, 3);
|
|
||||||
assert_eq!((-3.7_f64).round() as i64, -4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_round_decimals() {
|
|
||||||
let n = 2.71828_f64;
|
|
||||||
let decimals = 2;
|
|
||||||
let factor = 10_f64.powi(decimals);
|
|
||||||
let result = (n * factor).round() / factor;
|
|
||||||
assert!((result - 2.72).abs() < 0.001);
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sin() {
|
|
||||||
assert!((0.0_f64.sin() - 0.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_cos() {
|
|
||||||
assert!((0.0_f64.cos() - 1.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_log() {
|
|
||||||
assert!((100.0_f64.log10() - 2.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_exp() {
|
|
||||||
assert!((0.0_f64.exp() - 1.0).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_pi() {
|
|
||||||
assert!(std::f64::consts::PI > 3.14);
|
|
||||||
assert!(std::f64::consts::PI < 3.15);
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_csv_columns() {
|
|
||||||
let loader = McpCsvLoader::new("./work", "test");
|
|
||||||
|
|
||||||
let cols = loader.parse_csv_columns("name,type,command");
|
|
||||||
assert_eq!(cols, vec!["name", "type", "command"]);
|
|
||||||
|
|
||||||
let cols = loader.parse_csv_columns(
|
|
||||||
"filesystem,stdio,npx,\"-y @modelcontextprotocol/server-filesystem\"",
|
|
||||||
);
|
|
||||||
assert_eq!(cols.len(), 4);
|
|
||||||
assert_eq!(cols[3], "-y @modelcontextprotocol/server-filesystem");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_args() {
|
|
||||||
let loader = McpCsvLoader::new("./work", "test");
|
|
||||||
|
|
||||||
let args = loader.parse_args("-y @modelcontextprotocol/server-filesystem /data");
|
|
||||||
assert_eq!(
|
|
||||||
args,
|
|
||||||
vec!["-y", "@modelcontextprotocol/server-filesystem", "/data"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_infer_server_type() {
|
|
||||||
let loader = McpCsvLoader::new("./work", "test");
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
loader.infer_server_type("filesystem", "stdio", "npx"),
|
|
||||||
McpServerType::Filesystem
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
loader.infer_server_type("postgres", "stdio", "npx"),
|
|
||||||
McpServerType::Database
|
|
||||||
));
|
|
||||||
assert!(matches!(
|
|
||||||
loader.infer_server_type("myapi", "http", "https://api.example.com"),
|
|
||||||
McpServerType::Web
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_send_template_valid_email() {
|
|
||||||
let result = send_template_message("welcome", "user@example.com", "email", None);
|
|
||||||
assert!(result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_send_template_invalid_email() {
|
|
||||||
let result = send_template_message("welcome", "invalid-email", "email", None);
|
|
||||||
assert!(!result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_send_template_invalid_channel() {
|
|
||||||
let result = send_template_message("welcome", "user@example.com", "invalid", None);
|
|
||||||
assert!(!result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_send_template_batch() {
|
|
||||||
let mut recipients = Array::new();
|
|
||||||
recipients.push(Dynamic::from("user1@example.com"));
|
|
||||||
recipients.push(Dynamic::from("user2@example.com"));
|
|
||||||
|
|
||||||
let result = send_template_batch("welcome", &recipients, "email", None);
|
|
||||||
assert_eq!(result.get("total").unwrap().as_int().unwrap(), 2);
|
|
||||||
assert_eq!(result.get("sent").unwrap().as_int().unwrap(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_create_template() {
|
|
||||||
let result = create_message_template("test", "email", Some("Subject"), "Hello {{name}}!");
|
|
||||||
assert!(result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_create_template_empty_name() {
|
|
||||||
let result = create_message_template("", "email", None, "Content");
|
|
||||||
assert!(!result.get("success").unwrap().as_bool().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_template_variables() {
|
|
||||||
let content = "Hello {{name}}, your order {{order_id}} is ready!";
|
|
||||||
let vars = extract_template_variables(content);
|
|
||||||
assert_eq!(vars.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_template_variables_empty() {
|
|
||||||
let content = "Hello, no variables here!";
|
|
||||||
let vars = extract_template_variables(content);
|
|
||||||
assert!(vars.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_generate_message_id() {
|
|
||||||
let id = generate_message_id();
|
|
||||||
assert!(id.starts_with("msg_"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_model_router_new() {
|
|
||||||
let router = ModelRouter::new();
|
|
||||||
assert_eq!(router.default_model, "default");
|
|
||||||
assert!(router.models.is_empty());
|
|
||||||
assert_eq!(router.routing_strategy, RoutingStrategy::Manual);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_auto_routing_code() {
|
|
||||||
let mut router = ModelRouter::new();
|
|
||||||
router.models.insert(
|
|
||||||
"code".to_string(),
|
|
||||||
ModelConfig {
|
|
||||||
name: "code".to_string(),
|
|
||||||
url: "http://localhost:8081".to_string(),
|
|
||||||
model_path: "codellama.gguf".to_string(),
|
|
||||||
api_key: None,
|
|
||||||
max_tokens: None,
|
|
||||||
temperature: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.routing_strategy = RoutingStrategy::Auto;
|
|
||||||
|
|
||||||
let result = router.route_query("Help me debug this code");
|
|
||||||
assert_eq!(result, "code");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_auto_routing_quality() {
|
|
||||||
let mut router = ModelRouter::new();
|
|
||||||
router.models.insert(
|
|
||||||
"quality".to_string(),
|
|
||||||
ModelConfig {
|
|
||||||
name: "quality".to_string(),
|
|
||||||
url: "http://localhost:8081".to_string(),
|
|
||||||
model_path: "large-model.gguf".to_string(),
|
|
||||||
api_key: None,
|
|
||||||
max_tokens: None,
|
|
||||||
temperature: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.routing_strategy = RoutingStrategy::Auto;
|
|
||||||
|
|
||||||
let result =
|
|
||||||
router.route_query("Please analyze and compare these two approaches in detail");
|
|
||||||
assert_eq!(result, "quality");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_auto_routing_fast() {
|
|
||||||
let mut router = ModelRouter::new();
|
|
||||||
router.models.insert(
|
|
||||||
"fast".to_string(),
|
|
||||||
ModelConfig {
|
|
||||||
name: "fast".to_string(),
|
|
||||||
url: "http://localhost:8081".to_string(),
|
|
||||||
model_path: "small-model.gguf".to_string(),
|
|
||||||
api_key: None,
|
|
||||||
max_tokens: None,
|
|
||||||
temperature: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
router.routing_strategy = RoutingStrategy::Auto;
|
|
||||||
|
|
||||||
let result = router.route_query("What is AI?");
|
|
||||||
assert_eq!(result, "fast");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_routing_strategy_default() {
|
|
||||||
let strategy = RoutingStrategy::default();
|
|
||||||
assert_eq!(strategy, RoutingStrategy::Manual);
|
|
||||||
}
|
|
||||||
|
|
@ -1,208 +0,0 @@
|
||||||
use botserver::basic::keywords::on_change::{
|
|
||||||
detect_provider_from_email, is_cloud_path, parse_folder_path, sanitize_path_for_filename,
|
|
||||||
ChangeEventType, FolderChangeEvent, FolderMonitor, FolderProvider,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_folder_path_account() {
|
|
||||||
let (provider, email, path) = parse_folder_path("account://user@gmail.com/Documents/invoices");
|
|
||||||
assert_eq!(provider, FolderProvider::GDrive);
|
|
||||||
assert_eq!(email, Some("user@gmail.com".to_string()));
|
|
||||||
assert_eq!(path, "/Documents/invoices");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_folder_path_gdrive() {
|
|
||||||
let (provider, email, path) = parse_folder_path("gdrive:///shared/reports");
|
|
||||||
assert_eq!(provider, FolderProvider::GDrive);
|
|
||||||
assert_eq!(email, None);
|
|
||||||
assert_eq!(path, "/shared/reports");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_folder_path_onedrive() {
|
|
||||||
let (provider, email, path) = parse_folder_path("onedrive:///business/docs");
|
|
||||||
assert_eq!(provider, FolderProvider::OneDrive);
|
|
||||||
assert_eq!(email, None);
|
|
||||||
assert_eq!(path, "/business/docs");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_folder_path_dropbox() {
|
|
||||||
let (provider, email, path) = parse_folder_path("dropbox:///team/assets");
|
|
||||||
assert_eq!(provider, FolderProvider::Dropbox);
|
|
||||||
assert_eq!(email, None);
|
|
||||||
assert_eq!(path, "/team/assets");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_folder_path_local() {
|
|
||||||
let (provider, email, path) = parse_folder_path("/home/user/documents");
|
|
||||||
assert_eq!(provider, FolderProvider::Local);
|
|
||||||
assert_eq!(email, None);
|
|
||||||
assert_eq!(path, "/home/user/documents");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_cloud_path() {
|
|
||||||
assert!(is_cloud_path("account://user@gmail.com/docs"));
|
|
||||||
assert!(is_cloud_path("gdrive:///shared"));
|
|
||||||
assert!(is_cloud_path("onedrive:///files"));
|
|
||||||
assert!(is_cloud_path("dropbox:///folder"));
|
|
||||||
assert!(!is_cloud_path("/local/path"));
|
|
||||||
assert!(!is_cloud_path("./relative/path"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_folder_provider_from_str() {
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("gdrive"),
|
|
||||||
Some(FolderProvider::GDrive)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("GDRIVE"),
|
|
||||||
Some(FolderProvider::GDrive)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("googledrive"),
|
|
||||||
Some(FolderProvider::GDrive)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("onedrive"),
|
|
||||||
Some(FolderProvider::OneDrive)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("microsoft"),
|
|
||||||
Some(FolderProvider::OneDrive)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("dropbox"),
|
|
||||||
Some(FolderProvider::Dropbox)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("dbx"),
|
|
||||||
Some(FolderProvider::Dropbox)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("local"),
|
|
||||||
Some(FolderProvider::Local)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FolderProvider::from_str("filesystem"),
|
|
||||||
Some(FolderProvider::Local)
|
|
||||||
);
|
|
||||||
assert_eq!(FolderProvider::from_str("unknown"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_change_event_type_from_str() {
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("create"),
|
|
||||||
Some(ChangeEventType::Create)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("created"),
|
|
||||||
Some(ChangeEventType::Create)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("modify"),
|
|
||||||
Some(ChangeEventType::Modify)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("changed"),
|
|
||||||
Some(ChangeEventType::Modify)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("delete"),
|
|
||||||
Some(ChangeEventType::Delete)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("removed"),
|
|
||||||
Some(ChangeEventType::Delete)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("rename"),
|
|
||||||
Some(ChangeEventType::Rename)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ChangeEventType::from_str("move"),
|
|
||||||
Some(ChangeEventType::Move)
|
|
||||||
);
|
|
||||||
assert_eq!(ChangeEventType::from_str("invalid"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sanitize_path() {
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_path_for_filename("/home/user/docs"),
|
|
||||||
"_home_user_docs"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_path_for_filename("C:\\Users\\docs"),
|
|
||||||
"c__users_docs"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_path_for_filename("path with spaces"),
|
|
||||||
"path_with_spaces"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_folder_monitor_struct() {
|
|
||||||
let monitor = FolderMonitor {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
provider: "gdrive".to_string(),
|
|
||||||
account_email: Some("user@gmail.com".to_string()),
|
|
||||||
folder_path: "/my/folder".to_string(),
|
|
||||||
folder_id: Some("folder123".to_string()),
|
|
||||||
script_path: "on_change.rhai".to_string(),
|
|
||||||
is_active: true,
|
|
||||||
watch_subfolders: true,
|
|
||||||
event_types: vec!["create".to_string(), "modify".to_string()],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(monitor.provider, "gdrive");
|
|
||||||
assert!(monitor.is_active);
|
|
||||||
assert!(monitor.watch_subfolders);
|
|
||||||
assert_eq!(monitor.account_email, Some("user@gmail.com".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_folder_change_event_struct() {
|
|
||||||
let event = FolderChangeEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
monitor_id: Uuid::new_v4(),
|
|
||||||
event_type: "create".to_string(),
|
|
||||||
file_path: "/docs/new_file.pdf".to_string(),
|
|
||||||
file_id: Some("file123".to_string()),
|
|
||||||
file_name: Some("new_file.pdf".to_string()),
|
|
||||||
file_size: Some(1024),
|
|
||||||
mime_type: Some("application/pdf".to_string()),
|
|
||||||
old_path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(event.event_type, "create");
|
|
||||||
assert_eq!(event.file_size, Some(1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_provider_from_email() {
|
|
||||||
assert_eq!(
|
|
||||||
detect_provider_from_email("user@gmail.com"),
|
|
||||||
FolderProvider::GDrive
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_provider_from_email("user@outlook.com"),
|
|
||||||
FolderProvider::OneDrive
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_provider_from_email("user@hotmail.com"),
|
|
||||||
FolderProvider::OneDrive
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_provider_from_email("user@company.com"),
|
|
||||||
FolderProvider::GDrive
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
use botserver::basic::keywords::on_email::{
|
|
||||||
is_email_path, parse_email_path, sanitize_email_for_filename, EmailAttachment, EmailMonitor,
|
|
||||||
EmailReceivedEvent,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email_monitor_struct() {
|
|
||||||
let monitor = EmailMonitor {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
email_address: "test@example.com".to_string(),
|
|
||||||
script_path: "on_email_test.rhai".to_string(),
|
|
||||||
is_active: true,
|
|
||||||
filter_from: None,
|
|
||||||
filter_subject: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(monitor.email_address, "test@example.com");
|
|
||||||
assert!(monitor.is_active);
|
|
||||||
assert!(monitor.filter_from.is_none());
|
|
||||||
assert!(monitor.filter_subject.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email_monitor_with_filters() {
|
|
||||||
let monitor = EmailMonitor {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
bot_id: Uuid::new_v4(),
|
|
||||||
email_address: "orders@company.com".to_string(),
|
|
||||||
script_path: "on_email_orders.rhai".to_string(),
|
|
||||||
is_active: true,
|
|
||||||
filter_from: Some("supplier@vendor.com".to_string()),
|
|
||||||
filter_subject: Some("Invoice".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(monitor.email_address, "orders@company.com");
|
|
||||||
assert_eq!(monitor.filter_from, Some("supplier@vendor.com".to_string()));
|
|
||||||
assert_eq!(monitor.filter_subject, Some("Invoice".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email_attachment_struct() {
|
|
||||||
let attachment = EmailAttachment {
|
|
||||||
filename: "document.pdf".to_string(),
|
|
||||||
mime_type: "application/pdf".to_string(),
|
|
||||||
size: 1024,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(attachment.filename, "document.pdf");
|
|
||||||
assert_eq!(attachment.mime_type, "application/pdf");
|
|
||||||
assert_eq!(attachment.size, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email_received_event_struct() {
|
|
||||||
let event = EmailReceivedEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
monitor_id: Uuid::new_v4(),
|
|
||||||
message_uid: 12345,
|
|
||||||
message_id: Some("<msg123@example.com>".to_string()),
|
|
||||||
from_address: "sender@example.com".to_string(),
|
|
||||||
to_addresses: vec!["recipient@example.com".to_string()],
|
|
||||||
subject: Some("Test Subject".to_string()),
|
|
||||||
has_attachments: true,
|
|
||||||
attachments: vec![EmailAttachment {
|
|
||||||
filename: "file.pdf".to_string(),
|
|
||||||
mime_type: "application/pdf".to_string(),
|
|
||||||
size: 2048,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(event.message_uid, 12345);
|
|
||||||
assert_eq!(event.from_address, "sender@example.com");
|
|
||||||
assert!(event.has_attachments);
|
|
||||||
assert_eq!(event.attachments.len(), 1);
|
|
||||||
assert_eq!(event.attachments[0].filename, "file.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_email_path_basic() {
|
|
||||||
let result = parse_email_path("email://user@gmail.com");
|
|
||||||
assert!(result.is_some());
|
|
||||||
let (email, folder) = result.unwrap();
|
|
||||||
assert_eq!(email, "user@gmail.com");
|
|
||||||
assert!(folder.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_email_path_with_folder() {
|
|
||||||
let result = parse_email_path("email://user@gmail.com/INBOX");
|
|
||||||
assert!(result.is_some());
|
|
||||||
let (email, folder) = result.unwrap();
|
|
||||||
assert_eq!(email, "user@gmail.com");
|
|
||||||
assert_eq!(folder, Some("INBOX".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_email_path_invalid() {
|
|
||||||
assert!(parse_email_path("user@gmail.com").is_none());
|
|
||||||
assert!(parse_email_path("mailto:user@gmail.com").is_none());
|
|
||||||
assert!(parse_email_path("/local/path").is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_email_path() {
|
|
||||||
assert!(is_email_path("email://user@gmail.com"));
|
|
||||||
assert!(is_email_path("email://user@company.com/INBOX"));
|
|
||||||
assert!(!is_email_path("user@gmail.com"));
|
|
||||||
assert!(!is_email_path("mailto:user@gmail.com"));
|
|
||||||
assert!(!is_email_path("account://user@gmail.com"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sanitize_email_for_filename() {
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_email_for_filename("user@gmail.com"),
|
|
||||||
"user_at_gmail_com"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_email_for_filename("test.user@company.co.uk"),
|
|
||||||
"test_user_at_company_co_uk"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_email_for_filename("USER@EXAMPLE.COM"),
|
|
||||||
"user_at_example_com"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_email_event_without_attachments() {
|
|
||||||
let event = EmailReceivedEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
monitor_id: Uuid::new_v4(),
|
|
||||||
message_uid: 1,
|
|
||||||
message_id: None,
|
|
||||||
from_address: "no-reply@system.com".to_string(),
|
|
||||||
to_addresses: vec![],
|
|
||||||
subject: None,
|
|
||||||
has_attachments: false,
|
|
||||||
attachments: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(!event.has_attachments);
|
|
||||||
assert!(event.attachments.is_empty());
|
|
||||||
assert!(event.subject.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiple_to_addresses() {
|
|
||||||
let event = EmailReceivedEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
monitor_id: Uuid::new_v4(),
|
|
||||||
message_uid: 999,
|
|
||||||
message_id: Some("<multi@example.com>".to_string()),
|
|
||||||
from_address: "sender@example.com".to_string(),
|
|
||||||
to_addresses: vec![
|
|
||||||
"user1@example.com".to_string(),
|
|
||||||
"user2@example.com".to_string(),
|
|
||||||
"user3@example.com".to_string(),
|
|
||||||
],
|
|
||||||
subject: Some("Group Message".to_string()),
|
|
||||||
has_attachments: false,
|
|
||||||
attachments: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(event.to_addresses.len(), 3);
|
|
||||||
assert!(event
|
|
||||||
.to_addresses
|
|
||||||
.contains(&"user2@example.com".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiple_attachments() {
|
|
||||||
let attachments = vec![
|
|
||||||
EmailAttachment {
|
|
||||||
filename: "doc1.pdf".to_string(),
|
|
||||||
mime_type: "application/pdf".to_string(),
|
|
||||||
size: 1024,
|
|
||||||
},
|
|
||||||
EmailAttachment {
|
|
||||||
filename: "image.png".to_string(),
|
|
||||||
mime_type: "image/png".to_string(),
|
|
||||||
size: 2048,
|
|
||||||
},
|
|
||||||
EmailAttachment {
|
|
||||||
filename: "data.xlsx".to_string(),
|
|
||||||
mime_type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
||||||
.to_string(),
|
|
||||||
size: 4096,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(attachments.len(), 3);
|
|
||||||
assert_eq!(attachments[0].filename, "doc1.pdf");
|
|
||||||
assert_eq!(attachments[1].mime_type, "image/png");
|
|
||||||
assert_eq!(attachments[2].size, 4096);
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
use botserver::basic::keywords::play::{
|
|
||||||
detect_content_type, extract_title_from_source, ContentType, PlayOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_content_type_from_extension() {
|
|
||||||
assert_eq!(ContentType::from_extension("mp4"), ContentType::Video);
|
|
||||||
assert_eq!(ContentType::from_extension("MP3"), ContentType::Audio);
|
|
||||||
assert_eq!(ContentType::from_extension("png"), ContentType::Image);
|
|
||||||
assert_eq!(ContentType::from_extension("pdf"), ContentType::Pdf);
|
|
||||||
assert_eq!(ContentType::from_extension("rs"), ContentType::Code);
|
|
||||||
assert_eq!(
|
|
||||||
ContentType::from_extension("pptx"),
|
|
||||||
ContentType::Presentation
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ContentType::from_extension("xlsx"),
|
|
||||||
ContentType::Spreadsheet
|
|
||||||
);
|
|
||||||
assert_eq!(ContentType::from_extension("md"), ContentType::Markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_content_type_from_mime() {
|
|
||||||
assert_eq!(ContentType::from_mime("video/mp4"), ContentType::Video);
|
|
||||||
assert_eq!(ContentType::from_mime("audio/mpeg"), ContentType::Audio);
|
|
||||||
assert_eq!(ContentType::from_mime("image/png"), ContentType::Image);
|
|
||||||
assert_eq!(ContentType::from_mime("application/pdf"), ContentType::Pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_play_options_from_string() {
|
|
||||||
let opts = PlayOptions::from_string("autoplay,loop,muted");
|
|
||||||
assert!(opts.autoplay);
|
|
||||||
assert!(opts.loop_content);
|
|
||||||
assert!(opts.muted);
|
|
||||||
assert!(!opts.fullscreen);
|
|
||||||
assert!(opts.controls);
|
|
||||||
|
|
||||||
let opts = PlayOptions::from_string("fullscreen,nocontrols,start=10,end=60");
|
|
||||||
assert!(opts.fullscreen);
|
|
||||||
assert!(!opts.controls);
|
|
||||||
assert_eq!(opts.start_time, Some(10.0));
|
|
||||||
assert_eq!(opts.end_time, Some(60.0));
|
|
||||||
|
|
||||||
let opts = PlayOptions::from_string("theme=dark,zoom=1.5,page=3");
|
|
||||||
assert_eq!(opts.theme, Some("dark".to_string()));
|
|
||||||
assert_eq!(opts.zoom, Some(1.5));
|
|
||||||
assert_eq!(opts.page, Some(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_content_type() {
|
|
||||||
assert_eq!(
|
|
||||||
detect_content_type("https://youtube.com/watch?v=123"),
|
|
||||||
ContentType::Video
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_content_type("https://example.com/video.mp4"),
|
|
||||||
ContentType::Video
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_content_type("https://imgur.com/abc123"),
|
|
||||||
ContentType::Image
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
detect_content_type("presentation.pptx"),
|
|
||||||
ContentType::Presentation
|
|
||||||
);
|
|
||||||
assert_eq!(detect_content_type("report.pdf"), ContentType::Pdf);
|
|
||||||
assert_eq!(detect_content_type("main.rs"), ContentType::Code);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_title_from_source() {
|
|
||||||
assert_eq!(extract_title_from_source("documents/report.pdf"), "report");
|
|
||||||
assert_eq!(
|
|
||||||
extract_title_from_source("https://example.com/video.mp4?token=abc"),
|
|
||||||
"video"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
extract_title_from_source("presentation.pptx"),
|
|
||||||
"presentation"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_player_component() {
|
|
||||||
assert_eq!(ContentType::Video.player_component(), "video-player");
|
|
||||||
assert_eq!(ContentType::Audio.player_component(), "audio-player");
|
|
||||||
assert_eq!(ContentType::Image.player_component(), "image-viewer");
|
|
||||||
assert_eq!(ContentType::Pdf.player_component(), "pdf-viewer");
|
|
||||||
assert_eq!(ContentType::Code.player_component(), "code-viewer");
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn setup() {
|
|
||||||
clear_procedures();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_sub() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let input = r#"
|
|
||||||
x = 1
|
|
||||||
SUB MySub(a, b)
|
|
||||||
TALK a + b
|
|
||||||
END SUB
|
|
||||||
y = 2
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = preprocess_subs(input);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(!result.contains("SUB MySub"));
|
|
||||||
assert!(!result.contains("END SUB"));
|
|
||||||
assert!(result.contains("x = 1"));
|
|
||||||
assert!(result.contains("y = 2"));
|
|
||||||
|
|
||||||
|
|
||||||
assert!(has_procedure("MYSUB"));
|
|
||||||
let proc = get_procedure("MYSUB").unwrap();
|
|
||||||
assert_eq!(proc.params.len(), 2);
|
|
||||||
assert!(!proc.is_function);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_function() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let input = r#"
|
|
||||||
FUNCTION Add(a, b)
|
|
||||||
RETURN a + b
|
|
||||||
END FUNCTION
|
|
||||||
result = Add(1, 2)
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = preprocess_functions(input);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(!result.contains("FUNCTION Add"));
|
|
||||||
assert!(!result.contains("END FUNCTION"));
|
|
||||||
assert!(result.contains("result = Add(1, 2)"));
|
|
||||||
|
|
||||||
|
|
||||||
assert!(has_procedure("ADD"));
|
|
||||||
let proc = get_procedure("ADD").unwrap();
|
|
||||||
assert!(proc.is_function);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_sub_no_params() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let input = r#"
|
|
||||||
SUB PrintHello
|
|
||||||
TALK "Hello"
|
|
||||||
END SUB
|
|
||||||
"#;
|
|
||||||
|
|
||||||
preprocess_subs(input);
|
|
||||||
|
|
||||||
assert!(has_procedure("PRINTHELLO"));
|
|
||||||
let proc = get_procedure("PRINTHELLO").unwrap();
|
|
||||||
assert!(proc.params.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_call() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
|
|
||||||
let sub_input = r#"
|
|
||||||
SUB Greet(name)
|
|
||||||
TALK "Hello " + name
|
|
||||||
END SUB
|
|
||||||
"#;
|
|
||||||
preprocess_subs(sub_input);
|
|
||||||
|
|
||||||
|
|
||||||
let call_input = "CALL Greet(\"World\")";
|
|
||||||
let result = preprocess_calls(call_input);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(result.contains("let name = \"World\""));
|
|
||||||
assert!(result.contains("TALK \"Hello \" + name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_eval_bool_condition() {
|
|
||||||
assert!(eval_bool_condition(&Dynamic::from(true)));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from(false)));
|
|
||||||
assert!(eval_bool_condition(&Dynamic::from(1)));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from(0)));
|
|
||||||
assert!(eval_bool_condition(&Dynamic::from(1.5)));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from(0.0)));
|
|
||||||
assert!(eval_bool_condition(&Dynamic::from("hello")));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from("")));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from("false")));
|
|
||||||
assert!(!eval_bool_condition(&Dynamic::from("0")));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_clear_procedures() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let input = "SUB Test\n TALK \"test\"\nEND SUB";
|
|
||||||
preprocess_subs(input);
|
|
||||||
|
|
||||||
assert!(has_procedure("TEST"));
|
|
||||||
|
|
||||||
clear_procedures();
|
|
||||||
|
|
||||||
assert!(!has_procedure("TEST"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_full_pipeline() {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
let input = r#"
|
|
||||||
SUB SendGreeting(name, greeting)
|
|
||||||
TALK greeting + ", " + name + "!"
|
|
||||||
END SUB
|
|
||||||
|
|
||||||
FUNCTION Calculate(x, y)
|
|
||||||
result = x * y + 10
|
|
||||||
RETURN result
|
|
||||||
END FUNCTION
|
|
||||||
|
|
||||||
' Main code
|
|
||||||
CALL SendGreeting("User", "Hello")
|
|
||||||
total = Calculate(5, 3)
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let result = preprocess_procedures(input);
|
|
||||||
|
|
||||||
|
|
||||||
assert!(result.contains("let name = \"User\""));
|
|
||||||
assert!(result.contains("let greeting = \"Hello\""));
|
|
||||||
|
|
||||||
|
|
||||||
assert!(!result.contains("SUB SendGreeting"));
|
|
||||||
assert!(!result.contains("END SUB"));
|
|
||||||
assert!(!result.contains("FUNCTION Calculate"));
|
|
||||||
assert!(!result.contains("END FUNCTION"));
|
|
||||||
|
|
||||||
|
|
||||||
assert!(has_procedure("SENDGREETING"));
|
|
||||||
assert!(has_procedure("CALCULATE"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_qr_code_generation() {
|
|
||||||
|
|
||||||
|
|
||||||
let result = QrCode::new(b"https://example.com");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_qr_code_with_unicode() {
|
|
||||||
let result = QrCode::new("Hello 世界 🌍".as_bytes());
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_qr_code_long_data() {
|
|
||||||
let long_data = "A".repeat(1000);
|
|
||||||
let result = QrCode::new(long_data.as_bytes());
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_qr_code_url() {
|
|
||||||
let url = "https://example.com/path?param=value&other=123";
|
|
||||||
let result = QrCode::new(url.as_bytes());
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_qr_code_json() {
|
|
||||||
let json = r#"{"id": 123, "name": "Test", "active": true}"#;
|
|
||||||
let result = QrCode::new(json.as_bytes());
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_duration() {
|
|
||||||
|
|
||||||
assert!(parse_duration("30 days").is_ok());
|
|
||||||
assert!(parse_duration("1 hour").is_ok());
|
|
||||||
assert!(parse_duration("forever").is_ok());
|
|
||||||
assert!(parse_duration("5 minutes").is_ok());
|
|
||||||
assert!(parse_duration("invalid").is_err());
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_clean_value_for_type() {
|
|
||||||
assert_eq!(clean_value_for_type(&json!("test"), "text"), json!("test"));
|
|
||||||
assert_eq!(clean_value_for_type(&json!("42"), "integer"), json!(42));
|
|
||||||
assert_eq!(clean_value_for_type(&json!("3.14"), "numeric"), json!(3.14));
|
|
||||||
assert_eq!(clean_value_for_type(&json!("true"), "boolean"), json!(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_default_schema() {
|
|
||||||
let leads_schema = get_default_schema("leads");
|
|
||||||
assert!(leads_schema.is_array());
|
|
||||||
|
|
||||||
let tasks_schema = get_default_schema("tasks");
|
|
||||||
assert!(tasks_schema.is_array());
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_apply_template_variables() {
|
|
||||||
let template = "Hello {{name}}, your order {{order_id}} is ready!";
|
|
||||||
let vars = json!({
|
|
||||||
"name": "John",
|
|
||||||
"order_id": "12345"
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = apply_template_variables(template, &vars, "john@example.com").unwrap();
|
|
||||||
assert!(result.contains("John"));
|
|
||||||
assert!(result.contains("12345"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_extract_template_subject() {
|
|
||||||
let content = "Subject: Welcome to our service\n\nHello there!";
|
|
||||||
let subject = extract_template_subject(content);
|
|
||||||
assert_eq!(subject, Some("Welcome to our service".to_string()));
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_module_structure() {
|
|
||||||
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
use botserver::basic::keywords::set_schedule::parse_natural_schedule;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_minute() {
|
|
||||||
assert_eq!(parse_natural_schedule("every minute").unwrap(), "* * * * *");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_n_minutes() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 5 minutes").unwrap(),
|
|
||||||
"*/5 * * * *"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 15 minutes").unwrap(),
|
|
||||||
"*/15 * * * *"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 30 minutes").unwrap(),
|
|
||||||
"*/30 * * * *"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_hour() {
|
|
||||||
assert_eq!(parse_natural_schedule("every hour").unwrap(), "0 * * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("hourly").unwrap(), "0 * * * *");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_n_hours() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 2 hours").unwrap(),
|
|
||||||
"0 */2 * * *"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 6 hours").unwrap(),
|
|
||||||
"0 */6 * * *"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_day() {
|
|
||||||
assert_eq!(parse_natural_schedule("every day").unwrap(), "0 0 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("daily").unwrap(), "0 0 * * *");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_week() {
|
|
||||||
assert_eq!(parse_natural_schedule("every week").unwrap(), "0 0 * * 0");
|
|
||||||
assert_eq!(parse_natural_schedule("weekly").unwrap(), "0 0 * * 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_every_month() {
|
|
||||||
assert_eq!(parse_natural_schedule("every month").unwrap(), "0 0 1 * *");
|
|
||||||
assert_eq!(parse_natural_schedule("monthly").unwrap(), "0 0 1 * *");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_at_time() {
|
|
||||||
assert_eq!(parse_natural_schedule("at 9am").unwrap(), "0 9 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("at 9:30am").unwrap(), "30 9 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("at 2pm").unwrap(), "0 14 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("at 14:00").unwrap(), "0 14 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("at midnight").unwrap(), "0 0 * * *");
|
|
||||||
assert_eq!(parse_natural_schedule("at noon").unwrap(), "0 12 * * *");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_day_of_week() {
|
|
||||||
assert_eq!(parse_natural_schedule("every monday").unwrap(), "0 0 * * 1");
|
|
||||||
assert_eq!(parse_natural_schedule("every friday").unwrap(), "0 0 * * 5");
|
|
||||||
assert_eq!(parse_natural_schedule("every sunday").unwrap(), "0 0 * * 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_day_with_time() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every monday at 9am").unwrap(),
|
|
||||||
"0 9 * * 1"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every friday at 5pm").unwrap(),
|
|
||||||
"0 17 * * 5"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_weekdays() {
|
|
||||||
assert_eq!(parse_natural_schedule("weekdays").unwrap(), "0 0 * * 1-5");
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every weekday").unwrap(),
|
|
||||||
"0 0 * * 1-5"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("weekdays at 8am").unwrap(),
|
|
||||||
"0 8 * * 1-5"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_weekends() {
|
|
||||||
assert_eq!(parse_natural_schedule("weekends").unwrap(), "0 0 * * 0,6");
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every weekend").unwrap(),
|
|
||||||
"0 0 * * 0,6"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_combined() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every day at 9am").unwrap(),
|
|
||||||
"0 9 * * *"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every day at 6:30pm").unwrap(),
|
|
||||||
"30 18 * * *"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hour_range() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every hour from 9 to 17").unwrap(),
|
|
||||||
"0 9-17 * * *"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_business_hours() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("business hours").unwrap(),
|
|
||||||
"0 9-17 * * 1-5"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every 30 minutes during business hours").unwrap(),
|
|
||||||
"*/30 9-17 * * 1-5"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("every hour during business hours").unwrap(),
|
|
||||||
"0 9-17 * * 1-5"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_raw_cron_passthrough() {
|
|
||||||
assert_eq!(parse_natural_schedule("0 * * * *").unwrap(), "0 * * * *");
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("*/5 * * * *").unwrap(),
|
|
||||||
"*/5 * * * *"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_natural_schedule("0 9-17 * * 1-5").unwrap(),
|
|
||||||
"0 9-17 * * 1-5"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_input() {
|
|
||||||
assert!(parse_natural_schedule("potato salad").is_err());
|
|
||||||
assert!(parse_natural_schedule("every 100 minutes").is_err());
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_normalize_phone_us_10_digit() {
|
|
||||||
assert_eq!(normalize_phone_number("5551234567"), "+15551234567");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_normalize_phone_us_11_digit() {
|
|
||||||
assert_eq!(normalize_phone_number("15551234567"), "+15551234567");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_normalize_phone_with_plus() {
|
|
||||||
assert_eq!(normalize_phone_number("+15551234567"), "+15551234567");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_normalize_phone_with_formatting() {
|
|
||||||
assert_eq!(normalize_phone_number("+1 (555) 123-4567"), "+15551234567");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_normalize_phone_international() {
|
|
||||||
assert_eq!(normalize_phone_number("+44 7911 123456"), "+447911123456");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sms_provider_from_str() {
|
|
||||||
assert_eq!(SmsProvider::from("twilio"), SmsProvider::Twilio);
|
|
||||||
assert_eq!(SmsProvider::from("aws_sns"), SmsProvider::AwsSns);
|
|
||||||
assert_eq!(SmsProvider::from("vonage"), SmsProvider::Vonage);
|
|
||||||
assert_eq!(SmsProvider::from("nexmo"), SmsProvider::Vonage);
|
|
||||||
assert_eq!(SmsProvider::from("messagebird"), SmsProvider::MessageBird);
|
|
||||||
assert_eq!(
|
|
||||||
SmsProvider::from("custom"),
|
|
||||||
SmsProvider::Custom("custom".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_engagement_to_dynamic() {
|
|
||||||
let engagement = PostEngagement {
|
|
||||||
likes: 100,
|
|
||||||
comments: 20,
|
|
||||||
shares: 5,
|
|
||||||
views: 1000,
|
|
||||||
clicks: 50,
|
|
||||||
reach: 500,
|
|
||||||
};
|
|
||||||
|
|
||||||
let dynamic = engagement.to_dynamic();
|
|
||||||
assert!(dynamic.is_map());
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_module_structure() {
|
|
||||||
|
|
||||||
|
|
||||||
assert!(true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_schedule_time() {
|
|
||||||
let result = parse_schedule_time("2025-02-01 10:00");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_schedule_time("2025-02-01T10:00:00");
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let result = parse_schedule_time("invalid");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use botserver::basic::keywords::string_functions::{instr_impl, is_numeric_impl};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_instr_basic() {
|
|
||||||
assert_eq!(instr_impl(1, "Hello, World!", "World"), 8);
|
|
||||||
assert_eq!(instr_impl(1, "Hello, World!", "o"), 5);
|
|
||||||
assert_eq!(instr_impl(1, "Hello, World!", "xyz"), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_instr_with_start() {
|
|
||||||
assert_eq!(instr_impl(1, "one two one", "one"), 1);
|
|
||||||
assert_eq!(instr_impl(2, "one two one", "one"), 9);
|
|
||||||
assert_eq!(instr_impl(10, "one two one", "one"), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_instr_edge_cases() {
|
|
||||||
assert_eq!(instr_impl(1, "", "test"), 0);
|
|
||||||
assert_eq!(instr_impl(1, "test", ""), 0);
|
|
||||||
assert_eq!(instr_impl(1, "", ""), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_numeric_integers() {
|
|
||||||
assert!(is_numeric_impl("42"));
|
|
||||||
assert!(is_numeric_impl("-17"));
|
|
||||||
assert!(is_numeric_impl("0"));
|
|
||||||
assert!(is_numeric_impl(" 42 "));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_numeric_decimals() {
|
|
||||||
assert!(is_numeric_impl("3.14"));
|
|
||||||
assert!(is_numeric_impl("-0.5"));
|
|
||||||
assert!(is_numeric_impl(".25"));
|
|
||||||
assert!(is_numeric_impl("0.0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_numeric_scientific() {
|
|
||||||
assert!(is_numeric_impl("1e10"));
|
|
||||||
assert!(is_numeric_impl("2.5E-3"));
|
|
||||||
assert!(is_numeric_impl("-1.5e+2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_numeric_invalid() {
|
|
||||||
assert!(!is_numeric_impl(""));
|
|
||||||
assert!(!is_numeric_impl("abc"));
|
|
||||||
assert!(!is_numeric_impl("12abc"));
|
|
||||||
assert!(!is_numeric_impl("$100"));
|
|
||||||
assert!(!is_numeric_impl("1,000"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_switch_match_strings() {
|
|
||||||
let a = Dynamic::from("hello");
|
|
||||||
let b = Dynamic::from("hello");
|
|
||||||
let c = Dynamic::from("world");
|
|
||||||
|
|
||||||
assert!(switch_match_impl(&a, &b));
|
|
||||||
assert!(!switch_match_impl(&a, &c));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_switch_match_integers() {
|
|
||||||
let a = Dynamic::from(42_i64);
|
|
||||||
let b = Dynamic::from(42_i64);
|
|
||||||
let c = Dynamic::from(0_i64);
|
|
||||||
|
|
||||||
assert!(switch_match_impl(&a, &b));
|
|
||||||
assert!(!switch_match_impl(&a, &c));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_switch_match_floats() {
|
|
||||||
let a = Dynamic::from(3.14_f64);
|
|
||||||
let b = Dynamic::from(3.14_f64);
|
|
||||||
let c = Dynamic::from(2.71_f64);
|
|
||||||
|
|
||||||
assert!(switch_match_impl(&a, &b));
|
|
||||||
assert!(!switch_match_impl(&a, &c));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_switch_match_mixed_numeric() {
|
|
||||||
let int_val = Dynamic::from(42_i64);
|
|
||||||
let float_val = Dynamic::from(42.0_f64);
|
|
||||||
|
|
||||||
assert!(switch_match_impl(&int_val, &float_val));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_simple_switch() {
|
|
||||||
let input = r#"
|
|
||||||
SWITCH role
|
|
||||||
CASE "admin"
|
|
||||||
x = 1
|
|
||||||
CASE "user"
|
|
||||||
x = 2
|
|
||||||
DEFAULT
|
|
||||||
x = 0
|
|
||||||
END SWITCH
|
|
||||||
"#;
|
|
||||||
let output = preprocess_switch(input);
|
|
||||||
assert!(output.contains("__switch_expr_"));
|
|
||||||
assert!(output.contains("if"));
|
|
||||||
assert!(output.contains("else"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_preprocess_multiple_values() {
|
|
||||||
let input = r#"
|
|
||||||
SWITCH day
|
|
||||||
CASE "saturday", "sunday"
|
|
||||||
weekend = true
|
|
||||||
DEFAULT
|
|
||||||
weekend = false
|
|
||||||
END SWITCH
|
|
||||||
"#;
|
|
||||||
let output = preprocess_switch(input);
|
|
||||||
assert!(output.contains("||"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_table_definition() {
|
|
||||||
let source = r#"
|
|
||||||
TABLE Contacts ON maria
|
|
||||||
Id number key
|
|
||||||
Nome string(150)
|
|
||||||
Email string(255)
|
|
||||||
Telefone string(20)
|
|
||||||
END TABLE
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let tables = parse_table_definition(source).unwrap();
|
|
||||||
assert_eq!(tables.len(), 1);
|
|
||||||
|
|
||||||
let table = &tables[0];
|
|
||||||
assert_eq!(table.name, "Contacts");
|
|
||||||
assert_eq!(table.connection_name, "maria");
|
|
||||||
assert_eq!(table.fields.len(), 4);
|
|
||||||
|
|
||||||
assert_eq!(table.fields[0].name, "Id");
|
|
||||||
assert_eq!(table.fields[0].field_type, "number");
|
|
||||||
assert!(table.fields[0].is_key);
|
|
||||||
|
|
||||||
assert_eq!(table.fields[1].name, "Nome");
|
|
||||||
assert_eq!(table.fields[1].field_type, "string");
|
|
||||||
assert_eq!(table.fields[1].length, Some(150));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_field_with_precision() {
|
|
||||||
let field = parse_field_definition("Preco double(10,2)", 0).unwrap();
|
|
||||||
assert_eq!(field.name, "Preco");
|
|
||||||
assert_eq!(field.field_type, "double");
|
|
||||||
assert_eq!(field.length, Some(10));
|
|
||||||
assert_eq!(field.precision, Some(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_generate_create_table_sql() {
|
|
||||||
let table = TableDefinition {
|
|
||||||
name: "TestTable".to_string(),
|
|
||||||
connection_name: "default".to_string(),
|
|
||||||
fields: vec![
|
|
||||||
FieldDefinition {
|
|
||||||
name: "id".to_string(),
|
|
||||||
field_type: "number".to_string(),
|
|
||||||
length: None,
|
|
||||||
precision: None,
|
|
||||||
is_key: true,
|
|
||||||
is_nullable: false,
|
|
||||||
default_value: None,
|
|
||||||
reference_table: None,
|
|
||||||
field_order: 0,
|
|
||||||
},
|
|
||||||
FieldDefinition {
|
|
||||||
name: "name".to_string(),
|
|
||||||
field_type: "string".to_string(),
|
|
||||||
length: Some(100),
|
|
||||||
precision: None,
|
|
||||||
is_key: false,
|
|
||||||
is_nullable: true,
|
|
||||||
default_value: None,
|
|
||||||
reference_table: None,
|
|
||||||
field_order: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let sql = generate_create_table_sql(&table, "postgres");
|
|
||||||
assert!(sql.contains("CREATE TABLE IF NOT EXISTS TestTable"));
|
|
||||||
assert!(sql.contains("id INTEGER NOT NULL"));
|
|
||||||
assert!(sql.contains("name VARCHAR(100)"));
|
|
||||||
assert!(sql.contains("PRIMARY KEY (id)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_map_types() {
|
|
||||||
let field = FieldDefinition {
|
|
||||||
name: "test".to_string(),
|
|
||||||
field_type: "string".to_string(),
|
|
||||||
length: Some(50),
|
|
||||||
precision: None,
|
|
||||||
is_key: false,
|
|
||||||
is_nullable: true,
|
|
||||||
default_value: None,
|
|
||||||
reference_table: None,
|
|
||||||
field_order: 0,
|
|
||||||
};
|
|
||||||
assert_eq!(map_type_to_sql(&field, "postgres"), "VARCHAR(50)");
|
|
||||||
|
|
||||||
let date_field = FieldDefinition {
|
|
||||||
name: "created".to_string(),
|
|
||||||
field_type: "datetime".to_string(),
|
|
||||||
length: None,
|
|
||||||
precision: None,
|
|
||||||
is_key: false,
|
|
||||||
is_nullable: true,
|
|
||||||
default_value: None,
|
|
||||||
reference_table: None,
|
|
||||||
field_order: 0,
|
|
||||||
};
|
|
||||||
assert_eq!(map_type_to_sql(&date_field, "mysql"), "DATETIME");
|
|
||||||
assert_eq!(map_type_to_sql(&date_field, "postgres"), "TIMESTAMP");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sanitize_identifier() {
|
|
||||||
assert_eq!(sanitize_identifier("valid_name"), "valid_name");
|
|
||||||
assert_eq!(sanitize_identifier("DROP TABLE; --"), "DROPTABLE");
|
|
||||||
assert_eq!(sanitize_identifier("name123"), "name123");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_build_connection_string() {
|
|
||||||
let conn = ExternalConnection {
|
|
||||||
name: "test".to_string(),
|
|
||||||
driver: "mysql".to_string(),
|
|
||||||
server: "localhost".to_string(),
|
|
||||||
port: Some(3306),
|
|
||||||
database: "testdb".to_string(),
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "pass".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let conn_str = build_connection_string(&conn);
|
|
||||||
assert_eq!(conn_str, "mysql://user:pass@localhost:3306/testdb");
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_priority_to_int() {
|
|
||||||
assert_eq!(priority_to_int(Some("urgent")), 3);
|
|
||||||
assert_eq!(priority_to_int(Some("high")), 2);
|
|
||||||
assert_eq!(priority_to_int(Some("normal")), 1);
|
|
||||||
assert_eq!(priority_to_int(Some("low")), 0);
|
|
||||||
assert_eq!(priority_to_int(None), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_find_attendant_by_name() {
|
|
||||||
let attendants = vec![
|
|
||||||
Attendant {
|
|
||||||
id: "att-001".to_string(),
|
|
||||||
name: "John Smith".to_string(),
|
|
||||||
channel: "all".to_string(),
|
|
||||||
preferences: "sales".to_string(),
|
|
||||||
department: Some("commercial".to_string()),
|
|
||||||
aliases: vec!["johnny".to_string(), "js".to_string()],
|
|
||||||
status: AttendantStatus::Online,
|
|
||||||
},
|
|
||||||
Attendant {
|
|
||||||
id: "att-002".to_string(),
|
|
||||||
name: "Jane Doe".to_string(),
|
|
||||||
channel: "web".to_string(),
|
|
||||||
preferences: "support".to_string(),
|
|
||||||
department: Some("customer-service".to_string()),
|
|
||||||
aliases: vec![],
|
|
||||||
status: AttendantStatus::Online,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
let found = find_attendant(&attendants, Some("John Smith"), None);
|
|
||||||
assert!(found.is_some());
|
|
||||||
assert_eq!(found.unwrap().id, "att-001");
|
|
||||||
|
|
||||||
|
|
||||||
let found = find_attendant(&attendants, Some("john"), None);
|
|
||||||
assert!(found.is_some());
|
|
||||||
assert_eq!(found.unwrap().id, "att-001");
|
|
||||||
|
|
||||||
|
|
||||||
let found = find_attendant(&attendants, Some("johnny"), None);
|
|
||||||
assert!(found.is_some());
|
|
||||||
assert_eq!(found.unwrap().id, "att-001");
|
|
||||||
|
|
||||||
|
|
||||||
let found = find_attendant(&attendants, None, Some("customer-service"));
|
|
||||||
assert!(found.is_some());
|
|
||||||
assert_eq!(found.unwrap().id, "att-002");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_transfer_result_to_dynamic() {
|
|
||||||
let result = TransferResult {
|
|
||||||
success: true,
|
|
||||||
status: TransferStatus::Assigned,
|
|
||||||
queue_position: Some(1),
|
|
||||||
assigned_to: Some("att-001".to_string()),
|
|
||||||
assigned_to_name: Some("John Smith".to_string()),
|
|
||||||
estimated_wait_seconds: Some(30),
|
|
||||||
message: "Connected to John".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dynamic = result.to_dynamic();
|
|
||||||
let map = dynamic.try_cast::<Map>().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
map.get("success")
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
.try_cast::<bool>()
|
|
||||||
.unwrap(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
map.get("assigned_to_name")
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
.try_cast::<String>()
|
|
||||||
.unwrap(),
|
|
||||||
"John Smith"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_account_path() {
|
|
||||||
let result = parse_account_path("account://user@gmail.com/Documents/file.pdf");
|
|
||||||
assert!(result.is_some());
|
|
||||||
let (email, path) = result.unwrap();
|
|
||||||
assert_eq!(email, "user@gmail.com");
|
|
||||||
assert_eq!(path, "Documents/file.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_parse_account_path_invalid() {
|
|
||||||
assert!(parse_account_path("local/file.pdf").is_none());
|
|
||||||
assert!(parse_account_path("/absolute/path").is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_account_path() {
|
|
||||||
assert!(is_account_path("account://user@gmail.com/file.pdf"));
|
|
||||||
assert!(!is_account_path("local/file.pdf"));
|
|
||||||
assert!(!is_account_path("file.pdf"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_use_kb_syntax() {
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
|
|
||||||
assert!(engine
|
|
||||||
.register_custom_syntax(&["USE_KB", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
|
|
||||||
.is_ok());
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_url_sanitization() {
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_url_for_collection("https://docs.example.com/path"),
|
|
||||||
"docs_example_com_path"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_url_for_collection("http://test.site:8080"),
|
|
||||||
"test_site_8080"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_use_website_syntax() {
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
|
|
||||||
assert!(engine
|
|
||||||
.register_custom_syntax(&["USE_WEBSITE", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
|
|
||||||
assert!(engine
|
|
||||||
.register_custom_syntax(&["CLEAR_WEBSITES"], true, |_, _| Ok(Dynamic::UNIT))
|
|
||||||
.is_ok());
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_fact_key_generation() {
|
|
||||||
let fact_key = format!("fact_{}", Uuid::new_v4());
|
|
||||||
assert!(fact_key.starts_with("fact_"));
|
|
||||||
assert!(fact_key.len() > 5);
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
use rhai::{Array, Map};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_empty_string() {
|
|
||||||
let value = Dynamic::from("");
|
|
||||||
assert!(check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_non_empty_string() {
|
|
||||||
let value = Dynamic::from("hello");
|
|
||||||
assert!(!check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_empty_array() {
|
|
||||||
let value = Dynamic::from(Array::new());
|
|
||||||
assert!(check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_non_empty_array() {
|
|
||||||
let mut arr = Array::new();
|
|
||||||
arr.push(Dynamic::from(1));
|
|
||||||
let value = Dynamic::from(arr);
|
|
||||||
assert!(!check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_empty_map() {
|
|
||||||
let value = Dynamic::from(Map::new());
|
|
||||||
assert!(check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_unit() {
|
|
||||||
let value = Dynamic::UNIT;
|
|
||||||
assert!(check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_number_not_empty() {
|
|
||||||
let value = Dynamic::from(0);
|
|
||||||
assert!(!check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_bool_not_empty() {
|
|
||||||
let value = Dynamic::from(false);
|
|
||||||
assert!(!check_empty(&value));
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_isnull_unit() {
|
|
||||||
use rhai::Dynamic;
|
|
||||||
let value = Dynamic::UNIT;
|
|
||||||
assert!(value.is_unit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_isnull_not_unit() {
|
|
||||||
use rhai::Dynamic;
|
|
||||||
let value = Dynamic::from("test");
|
|
||||||
assert!(!value.is_unit());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_isnull_number() {
|
|
||||||
use rhai::Dynamic;
|
|
||||||
let value = Dynamic::from(42);
|
|
||||||
assert!(!value.is_unit());
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_nvl_logic() {
|
|
||||||
let value = "";
|
|
||||||
let default = "default";
|
|
||||||
let result = if value.is_empty() { default } else { value };
|
|
||||||
assert_eq!(result, "default");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_nvl_with_value() {
|
|
||||||
let value = "actual";
|
|
||||||
let default = "default";
|
|
||||||
let result = if value.is_empty() { default } else { value };
|
|
||||||
assert_eq!(result, "actual");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_iif_true() {
|
|
||||||
let condition = true;
|
|
||||||
let result = if condition { "yes" } else { "no" };
|
|
||||||
assert_eq!(result, "yes");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_iif_false() {
|
|
||||||
let condition = false;
|
|
||||||
let result = if condition { "yes" } else { "no" };
|
|
||||||
assert_eq!(result, "no");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_choose() {
|
|
||||||
let index = 2;
|
|
||||||
let values = vec!["first", "second", "third"];
|
|
||||||
let result = values.get((index - 1) as usize).unwrap_or(&"");
|
|
||||||
assert_eq!(*result, "second");
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn test_val_parsing() {
|
|
||||||
assert_eq!("123.45".trim().parse::<f64>().unwrap_or(0.0), 123.45);
|
|
||||||
assert_eq!(" 456 ".trim().parse::<f64>().unwrap_or(0.0), 456.0);
|
|
||||||
assert_eq!("abc".trim().parse::<f64>().unwrap_or(0.0), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_cint_rounding() {
|
|
||||||
assert_eq!(2.4_f64.round() as i64, 2);
|
|
||||||
assert_eq!(2.5_f64.round() as i64, 3);
|
|
||||||
assert_eq!(2.6_f64.round() as i64, 3);
|
|
||||||
assert_eq!((-2.5_f64).round() as i64, -3);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_type_name() {
|
|
||||||
assert_eq!(get_type_name(&Dynamic::UNIT), "null");
|
|
||||||
assert_eq!(get_type_name(&Dynamic::from(true)), "boolean");
|
|
||||||
assert_eq!(get_type_name(&Dynamic::from(42_i64)), "integer");
|
|
||||||
assert_eq!(get_type_name(&Dynamic::from(3.14_f64)), "float");
|
|
||||||
assert_eq!(get_type_name(&Dynamic::from("hello")), "string");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_is_numeric() {
|
|
||||||
assert!(is_numeric(&Dynamic::from(42_i64)));
|
|
||||||
assert!(is_numeric(&Dynamic::from(3.14_f64)));
|
|
||||||
assert!(is_numeric(&Dynamic::from("123")));
|
|
||||||
assert!(is_numeric(&Dynamic::from("3.14")));
|
|
||||||
assert!(!is_numeric(&Dynamic::from("hello")));
|
|
||||||
assert!(!is_numeric(&Dynamic::from(true)));
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_degrees_to_compass() {
|
|
||||||
assert_eq!(degrees_to_compass(0.0), "N");
|
|
||||||
assert_eq!(degrees_to_compass(45.0), "NE");
|
|
||||||
assert_eq!(degrees_to_compass(90.0), "E");
|
|
||||||
assert_eq!(degrees_to_compass(180.0), "S");
|
|
||||||
assert_eq!(degrees_to_compass(270.0), "W");
|
|
||||||
assert_eq!(degrees_to_compass(315.0), "NW");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_format_weather_response() {
|
|
||||||
let weather = WeatherData {
|
|
||||||
location: "London".to_string(),
|
|
||||||
temperature: 15.0,
|
|
||||||
temperature_unit: "°C".to_string(),
|
|
||||||
description: "Partly cloudy".to_string(),
|
|
||||||
humidity: 65,
|
|
||||||
wind_speed: 3.5,
|
|
||||||
wind_direction: "NE".to_string(),
|
|
||||||
feels_like: 14.0,
|
|
||||||
pressure: 1013,
|
|
||||||
visibility: 10.0,
|
|
||||||
uv_index: Some(3.0),
|
|
||||||
forecast: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = format_weather_response(&weather);
|
|
||||||
assert!(response.contains("London"));
|
|
||||||
assert!(response.contains("15.0"));
|
|
||||||
assert!(response.contains("Partly cloudy"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rhai::Dynamic;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_webhook_request_to_dynamic() {
|
|
||||||
let mut headers = std::collections::HashMap::new();
|
|
||||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
||||||
|
|
||||||
let mut params = std::collections::HashMap::new();
|
|
||||||
params.insert("id".to_string(), "123".to_string());
|
|
||||||
|
|
||||||
let request = WebhookRequest::new(
|
|
||||||
"POST",
|
|
||||||
headers,
|
|
||||||
params,
|
|
||||||
json!({"order": "test"}),
|
|
||||||
"/webhook/order-received",
|
|
||||||
);
|
|
||||||
|
|
||||||
let dynamic = request.to_dynamic();
|
|
||||||
assert!(dynamic.is_map());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_webhook_response_from_dynamic() {
|
|
||||||
let mut map = rhai::Map::new();
|
|
||||||
map.insert("status".into(), Dynamic::from(201_i64));
|
|
||||||
map.insert(
|
|
||||||
"body".into(),
|
|
||||||
Dynamic::from(json!({"message": "created"}).to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let dynamic = Dynamic::from(map);
|
|
||||||
let response = WebhookResponse::from_dynamic(&dynamic);
|
|
||||||
|
|
||||||
assert_eq!(response.status, 201);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_json_to_dynamic_and_back() {
|
|
||||||
let original = json!({
|
|
||||||
"name": "test",
|
|
||||||
"count": 42,
|
|
||||||
"active": true,
|
|
||||||
"items": [1, 2, 3]
|
|
||||||
});
|
|
||||||
|
|
||||||
let dynamic = json_to_dynamic(&original);
|
|
||||||
let back = dynamic_to_json(&dynamic);
|
|
||||||
|
|
||||||
assert_eq!(original["name"], back["name"]);
|
|
||||||
assert_eq!(original["count"], back["count"]);
|
|
||||||
assert_eq!(original["active"], back["active"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_webhook_response_default() {
|
|
||||||
let response = WebhookResponse::default();
|
|
||||||
assert_eq!(response.status, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_webhook_response_error() {
|
|
||||||
let response = WebhookResponse::error(404, "Not found");
|
|
||||||
assert_eq!(response.status, 404);
|
|
||||||
assert_eq!(response.body["error"], "Not found");
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
|
|
||||||
mod basic_compiler_goto_transform;
|
|
||||||
mod basic_keywords_a2a_protocol;
|
|
||||||
mod basic_keywords_add_bot;
|
|
||||||
mod basic_keywords_add_member;
|
|
||||||
mod basic_keywords_add_suggestion;
|
|
||||||
mod basic_keywords_agent_reflection;
|
|
||||||
mod basic_keywords_arrays_contains;
|
|
||||||
mod basic_keywords_arrays_push_pop;
|
|
||||||
mod basic_keywords_arrays;
|
|
||||||
mod basic_keywords_arrays_slice;
|
|
||||||
mod basic_keywords_arrays_sort;
|
|
||||||
mod basic_keywords_arrays_unique;
|
|
||||||
mod basic_keywords_book;
|
|
||||||
mod basic_keywords_card;
|
|
||||||
mod basic_keywords_clear_kb;
|
|
||||||
mod basic_keywords_code_sandbox;
|
|
||||||
mod basic_keywords_core_functions;
|
|
||||||
mod basic_keywords_create_task;
|
|
||||||
mod basic_keywords_crm_attendance;
|
|
||||||
mod basic_keywords_crm_score_lead;
|
|
||||||
mod basic_keywords_data_operations;
|
|
||||||
mod basic_keywords_datetime_dateadd;
|
|
||||||
mod basic_keywords_datetime_extract;
|
|
||||||
mod basic_keywords_datetime_now;
|
|
||||||
mod basic_keywords_episodic_memory;
|
|
||||||
mod basic_keywords_errors_on_error;
|
|
||||||
mod basic_keywords_errors;
|
|
||||||
mod basic_keywords_errors_throw;
|
|
||||||
mod basic_keywords_file_operations;
|
|
||||||
mod basic_keywords_hear_talk;
|
|
||||||
mod basic_keywords_http_operations;
|
|
||||||
mod basic_keywords_human_approval;
|
|
||||||
mod basic_keywords_import_export;
|
|
||||||
mod basic_keywords_kb_statistics;
|
|
||||||
mod basic_keywords_knowledge_graph;
|
|
||||||
mod basic_keywords_lead_scoring;
|
|
||||||
mod basic_keywords_llm_macros;
|
|
||||||
mod basic_keywords_math_abs;
|
|
||||||
mod basic_keywords_math_aggregate;
|
|
||||||
mod basic_keywords_math_basic_math;
|
|
||||||
mod basic_keywords_math_minmax;
|
|
||||||
mod basic_keywords_math_random;
|
|
||||||
mod basic_keywords_math_round;
|
|
||||||
mod basic_keywords_math_trig;
|
|
||||||
mod basic_keywords_mcp_directory;
|
|
||||||
mod basic_keywords_messaging_send_template;
|
|
||||||
mod basic_keywords_model_routing;
|
|
||||||
mod basic_keywords_on_change;
|
|
||||||
mod basic_keywords_on_email;
|
|
||||||
mod basic_keywords_play;
|
|
||||||
mod basic_keywords_procedures;
|
|
||||||
mod basic_keywords_qrcode;
|
|
||||||
mod basic_keywords_remember;
|
|
||||||
mod basic_keywords_save_from_unstructured;
|
|
||||||
mod basic_keywords_send_mail;
|
|
||||||
mod basic_keywords_send_template;
|
|
||||||
mod basic_keywords_set_schedule;
|
|
||||||
mod basic_keywords_sms;
|
|
||||||
mod basic_keywords_social_get_metrics;
|
|
||||||
mod basic_keywords_social_media;
|
|
||||||
mod basic_keywords_social_post_to_scheduled;
|
|
||||||
mod basic_keywords_string_functions;
|
|
||||||
mod basic_keywords_switch_case;
|
|
||||||
mod basic_keywords_table_definition;
|
|
||||||
mod basic_keywords_transfer_to_human;
|
|
||||||
mod basic_keywords_use_account;
|
|
||||||
mod basic_keywords_use_kb;
|
|
||||||
mod basic_keywords_user_memory;
|
|
||||||
mod basic_keywords_use_website;
|
|
||||||
mod basic_keywords_validation_isempty;
|
|
||||||
mod basic_keywords_validation_isnull;
|
|
||||||
mod basic_keywords_validation_nvl_iif;
|
|
||||||
mod basic_keywords_validation_str_val;
|
|
||||||
mod basic_keywords_validation_typeof_check;
|
|
||||||
mod basic_keywords_weather;
|
|
||||||
mod basic_keywords_webhook;
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_event_to_ical_roundtrip() {
|
|
||||||
let event = CalendarEvent {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
title: "Test Meeting".to_string(),
|
|
||||||
description: Some("A test meeting".to_string()),
|
|
||||||
start_time: Utc::now(),
|
|
||||||
end_time: Utc::now() + chrono::Duration::hours(1),
|
|
||||||
location: Some("Room 101".to_string()),
|
|
||||||
attendees: vec!["user@example.com".to_string()],
|
|
||||||
organizer: "organizer@example.com".to_string(),
|
|
||||||
reminder_minutes: Some(15),
|
|
||||||
recurrence: None,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let ical = event.to_ical();
|
|
||||||
assert_eq!(ical.get_summary(), Some("Test Meeting"));
|
|
||||||
assert_eq!(ical.get_location(), Some("Room 101"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_export_import_ical() {
|
|
||||||
let mut engine = CalendarEngine::new();
|
|
||||||
engine.create_event(CalendarEventInput {
|
|
||||||
title: "Event 1".to_string(),
|
|
||||||
description: None,
|
|
||||||
start_time: Utc::now(),
|
|
||||||
end_time: Utc::now() + chrono::Duration::hours(1),
|
|
||||||
location: None,
|
|
||||||
attendees: vec![],
|
|
||||||
organizer: "test@example.com".to_string(),
|
|
||||||
reminder_minutes: None,
|
|
||||||
recurrence: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let ical = engine.export_ical("Test Calendar");
|
|
||||||
assert!(ical.contains("BEGIN:VCALENDAR"));
|
|
||||||
assert!(ical.contains("Event 1"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
mod calendar;
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_compliance_monitor() {
|
|
||||||
let monitor = ComplianceMonitor::new(vec![ComplianceFramework::GDPR]);
|
|
||||||
let results = monitor.run_checks().await.unwrap();
|
|
||||||
assert!(!results.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_compliance_score() {
|
|
||||||
let results = vec![
|
|
||||||
ComplianceCheckResult {
|
|
||||||
framework: ComplianceFramework::GDPR,
|
|
||||||
control_id: "test_1".to_string(),
|
|
||||||
control_name: "Test Control 1".to_string(),
|
|
||||||
status: ComplianceStatus::Compliant,
|
|
||||||
score: 100.0,
|
|
||||||
checked_at: Utc::now(),
|
|
||||||
issues: vec![],
|
|
||||||
evidence: vec![],
|
|
||||||
},
|
|
||||||
ComplianceCheckResult {
|
|
||||||
framework: ComplianceFramework::GDPR,
|
|
||||||
control_id: "test_2".to_string(),
|
|
||||||
control_name: "Test Control 2".to_string(),
|
|
||||||
status: ComplianceStatus::Compliant,
|
|
||||||
score: 90.0,
|
|
||||||
checked_at: Utc::now(),
|
|
||||||
issues: vec![],
|
|
||||||
evidence: vec![],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let score = ComplianceMonitor::calculate_compliance_score(&results);
|
|
||||||
assert_eq!(score, 95.0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_pattern_matching() {
|
|
||||||
let scanner = CodeScanner::new("/tmp/test");
|
|
||||||
|
|
||||||
|
|
||||||
let password_pattern = scanner
|
|
||||||
.patterns
|
|
||||||
.iter()
|
|
||||||
.find(|p| matches!(p.issue_type, IssueType::PasswordInConfig))
|
|
||||||
.unwrap();
|
|
||||||
assert!(password_pattern.regex.is_match(r#"password = "secret123""#));
|
|
||||||
assert!(password_pattern.regex.is_match(r#"PASSWORD = 'mypass'"#));
|
|
||||||
|
|
||||||
|
|
||||||
let underscore_pattern = scanner
|
|
||||||
.patterns
|
|
||||||
.iter()
|
|
||||||
.find(|p| matches!(p.issue_type, IssueType::UnderscoreInKeyword))
|
|
||||||
.unwrap();
|
|
||||||
assert!(underscore_pattern.regex.is_match("GET_BOT_MEMORY"));
|
|
||||||
assert!(underscore_pattern.regex.is_match("SET_USER_MEMORY"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_severity_ordering() {
|
|
||||||
assert!(IssueSeverity::Critical > IssueSeverity::High);
|
|
||||||
assert!(IssueSeverity::High > IssueSeverity::Medium);
|
|
||||||
assert!(IssueSeverity::Medium > IssueSeverity::Low);
|
|
||||||
assert!(IssueSeverity::Low > IssueSeverity::Info);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_stats_merge() {
|
|
||||||
let mut stats1 = ScanStats {
|
|
||||||
critical: 1,
|
|
||||||
high: 2,
|
|
||||||
medium: 3,
|
|
||||||
low: 4,
|
|
||||||
info: 5,
|
|
||||||
total: 15,
|
|
||||||
};
|
|
||||||
|
|
||||||
let stats2 = ScanStats {
|
|
||||||
critical: 1,
|
|
||||||
high: 1,
|
|
||||||
medium: 1,
|
|
||||||
low: 1,
|
|
||||||
info: 1,
|
|
||||||
total: 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
stats1.merge(&stats2);
|
|
||||||
|
|
||||||
assert_eq!(stats1.critical, 2);
|
|
||||||
assert_eq!(stats1.high, 3);
|
|
||||||
assert_eq!(stats1.total, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_csv_escape() {
|
|
||||||
assert_eq!(escape_csv("simple"), "simple");
|
|
||||||
assert_eq!(escape_csv("with,comma"), "\"with,comma\"");
|
|
||||||
assert_eq!(escape_csv("with\"quote"), "\"with\"\"quote\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_redact_sensitive() {
|
|
||||||
let scanner = CodeScanner::new("/tmp/test");
|
|
||||||
|
|
||||||
let line = r#"password = "supersecretpassword123""#;
|
|
||||||
let redacted = scanner.redact_sensitive(line);
|
|
||||||
assert!(redacted.contains("***REDACTED***"));
|
|
||||||
assert!(!redacted.contains("supersecretpassword123"));
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
mod compliance_code_scanner;
|
|
||||||
mod compliance;
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = WizardConfig::default();
|
|
||||||
assert_eq!(config.llm_provider, LlmProvider::None);
|
|
||||||
assert!(!config.components.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_slug_generation() {
|
|
||||||
let mut config = WizardConfig::default();
|
|
||||||
config.organization.name = "My Test Company".to_string();
|
|
||||||
config.organization.slug = config
|
|
||||||
.organization
|
|
||||||
.name
|
|
||||||
.to_lowercase()
|
|
||||||
.replace(' ', "-")
|
|
||||||
.chars()
|
|
||||||
.filter(|c| c.is_alphanumeric() || *c == '-')
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
assert_eq!(config.organization.slug, "my-test-company");
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
mod console_wizard;
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_sanitize_bot_name() {
|
|
||||||
let manager = BotManager::new("", "", "", "", PathBuf::new());
|
|
||||||
|
|
||||||
assert_eq!(manager.sanitize_bot_name("My Bot"), "mybot");
|
|
||||||
assert_eq!(manager.sanitize_bot_name("test-bot"), "test-bot");
|
|
||||||
assert_eq!(manager.sanitize_bot_name("Bot 123"), "bot123");
|
|
||||||
assert_eq!(manager.sanitize_bot_name("--invalid--"), "invalid");
|
|
||||||
assert_eq!(manager.sanitize_bot_name("my_bot_name"), "my_bot_name");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_bot_config_default() {
|
|
||||||
let settings = BotSettings::default();
|
|
||||||
assert!(settings.knowledge_bases.is_empty());
|
|
||||||
assert!(settings.channels.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_bot_status_display() {
|
|
||||||
assert_eq!(format!("{}", BotStatus::Active), "Active");
|
|
||||||
assert_eq!(format!("{}", BotStatus::Creating), "Creating");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_bot_route_from_config() {
|
|
||||||
let config = BotConfig {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
name: "testbot".to_string(),
|
|
||||||
display_name: "Test Bot".to_string(),
|
|
||||||
org_id: Uuid::new_v4(),
|
|
||||||
org_slug: "myorg".to_string(),
|
|
||||||
template: None,
|
|
||||||
status: BotStatus::Active,
|
|
||||||
bucket: "myorg_testbot".to_string(),
|
|
||||||
custom_ui: Some("custom".to_string()),
|
|
||||||
settings: BotSettings::default(),
|
|
||||||
access: BotAccess::default(),
|
|
||||||
created_at: Utc::now(),
|
|
||||||
updated_at: Utc::now(),
|
|
||||||
created_by: Uuid::new_v4(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let route = BotRoute::from(&config);
|
|
||||||
assert_eq!(route.name, "testbot");
|
|
||||||
assert_eq!(route.org_slug, "myorg");
|
|
||||||
assert_eq!(route.bucket, "myorg_testbot");
|
|
||||||
assert_eq!(route.custom_ui, Some("custom".to_string()));
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = ModelRoutingConfig::default();
|
|
||||||
assert_eq!(config.routing_strategy, RoutingStrategy::Default);
|
|
||||||
assert_eq!(config.default_model, "gpt-4o");
|
|
||||||
assert!(config.fallback_enabled);
|
|
||||||
assert!(!config.fallback_order.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_routing_strategy_from_str() {
|
|
||||||
assert_eq!(RoutingStrategy::from("default"), RoutingStrategy::Default);
|
|
||||||
assert_eq!(
|
|
||||||
RoutingStrategy::from("task-based"),
|
|
||||||
RoutingStrategy::TaskBased
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
RoutingStrategy::from("round-robin"),
|
|
||||||
RoutingStrategy::RoundRobin
|
|
||||||
);
|
|
||||||
assert_eq!(RoutingStrategy::from("latency"), RoutingStrategy::Latency);
|
|
||||||
assert_eq!(RoutingStrategy::from("cost"), RoutingStrategy::Cost);
|
|
||||||
assert_eq!(RoutingStrategy::from("custom"), RoutingStrategy::Custom);
|
|
||||||
assert_eq!(RoutingStrategy::from("unknown"), RoutingStrategy::Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_model_for_task_default_strategy() {
|
|
||||||
let config = ModelRoutingConfig::default();
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Simple), "gpt-4o");
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Complex), "gpt-4o");
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Code), "gpt-4o");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_model_for_task_based_strategy() {
|
|
||||||
let config = ModelRoutingConfig {
|
|
||||||
routing_strategy: RoutingStrategy::TaskBased,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Simple), "gpt-4o-mini");
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Complex), "gpt-4o");
|
|
||||||
assert_eq!(config.get_model_for_task(TaskType::Code), "gpt-4o");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_fallback_model() {
|
|
||||||
let config = ModelRoutingConfig::default();
|
|
||||||
assert_eq!(config.get_fallback_model("gpt-4o"), Some("gpt-4o-mini"));
|
|
||||||
assert_eq!(
|
|
||||||
config.get_fallback_model("gpt-4o-mini"),
|
|
||||||
Some("gpt-3.5-turbo")
|
|
||||||
);
|
|
||||||
assert_eq!(config.get_fallback_model("gpt-3.5-turbo"), None);
|
|
||||||
assert_eq!(config.get_fallback_model("unknown-model"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_fallback_model_disabled() {
|
|
||||||
let config = ModelRoutingConfig {
|
|
||||||
fallback_enabled: false,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(config.get_fallback_model("gpt-4o"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_get_all_models() {
|
|
||||||
let config = ModelRoutingConfig::default();
|
|
||||||
let models = config.get_all_models();
|
|
||||||
assert!(models.contains(&"gpt-4o"));
|
|
||||||
assert!(models.contains(&"gpt-4o-mini"));
|
|
||||||
assert!(models.contains(&"gpt-3.5-turbo"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_routing_strategy_display() {
|
|
||||||
assert_eq!(format!("{}", RoutingStrategy::Default), "default");
|
|
||||||
assert_eq!(format!("{}", RoutingStrategy::TaskBased), "task-based");
|
|
||||||
assert_eq!(format!("{}", RoutingStrategy::RoundRobin), "round-robin");
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = SseConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.heartbeat_seconds, 30);
|
|
||||||
assert_eq!(config.max_connections, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_can_accept_connection() {
|
|
||||||
let config = SseConfig::default();
|
|
||||||
assert!(config.can_accept_connection(0));
|
|
||||||
assert!(config.can_accept_connection(999));
|
|
||||||
assert!(!config.can_accept_connection(1000));
|
|
||||||
assert!(!config.can_accept_connection(1001));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_can_accept_connection_disabled() {
|
|
||||||
let config = SseConfig {
|
|
||||||
enabled: false,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert!(!config.can_accept_connection(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_heartbeat_duration() {
|
|
||||||
let config = SseConfig {
|
|
||||||
heartbeat_seconds: 45,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
config.heartbeat_duration(),
|
|
||||||
std::time::Duration::from_secs(45)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_default_config() {
|
|
||||||
let config = UserMemoryConfig::default();
|
|
||||||
assert!(config.enabled);
|
|
||||||
assert_eq!(config.max_keys, 100);
|
|
||||||
assert_eq!(config.default_ttl, 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_can_add_key() {
|
|
||||||
let config = UserMemoryConfig::default();
|
|
||||||
assert!(config.can_add_key(0));
|
|
||||||
assert!(config.can_add_key(99));
|
|
||||||
assert!(!config.can_add_key(100));
|
|
||||||
assert!(!config.can_add_key(101));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_can_add_key_disabled() {
|
|
||||||
let config = UserMemoryConfig {
|
|
||||||
enabled: false,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert!(!config.can_add_key(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_ttl_duration() {
|
|
||||||
let config = UserMemoryConfig {
|
|
||||||
default_ttl: 3600,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
config.ttl_duration(),
|
|
||||||
Some(std::time::Duration::from_secs(3600))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_ttl_duration_no_expiration() {
|
|
||||||
let config = UserMemoryConfig {
|
|
||||||
default_ttl: 0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert_eq!(config.ttl_duration(), None);
|
|
||||||
assert!(!config.has_expiration());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_has_expiration() {
|
|
||||||
let config = UserMemoryConfig::default();
|
|
||||||
assert!(config.has_expiration());
|
|
||||||
|
|
||||||
let no_expiry = UserMemoryConfig {
|
|
||||||
default_ttl: 0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
assert!(!no_expiry.has_expiration());
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use tempfile::TempDir;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_kb_manager_creation() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let manager = KnowledgeBaseManager::new(temp_dir.path());
|
|
||||||
|
|
||||||
|
|
||||||
assert!(manager.processor.chunk_size() == 1000);
|
|
||||||
assert!(manager.processor.chunk_overlap() == 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_collection_naming() {
|
|
||||||
let bot_name = "testbot";
|
|
||||||
let kb_name = "docs";
|
|
||||||
let collection_name = format!("{}_{}", bot_name, kb_name);
|
|
||||||
assert_eq!(collection_name, "testbot_docs");
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_chunk_creation() {
|
|
||||||
let processor = DocumentProcessor::default();
|
|
||||||
let text = "This is a test document with some content that needs to be chunked properly. "
|
|
||||||
.repeat(20);
|
|
||||||
let chunks = processor.create_chunks(&text, Path::new("test.txt"));
|
|
||||||
|
|
||||||
|
|
||||||
assert!(!chunks.is_empty());
|
|
||||||
|
|
||||||
|
|
||||||
for chunk in &chunks {
|
|
||||||
assert!(chunk.content.len() <= processor.chunk_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if chunks.len() > 1 {
|
|
||||||
let first_end = &chunks[0].content[chunks[0].content.len().saturating_sub(100)..];
|
|
||||||
let second_start = &chunks[1].content[..100.min(chunks[1].content.len())];
|
|
||||||
|
|
||||||
|
|
||||||
assert!(first_end.chars().any(|c| second_start.contains(c)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_format_detection() {
|
|
||||||
assert_eq!(
|
|
||||||
DocumentFormat::from_extension(Path::new("test.pdf")),
|
|
||||||
Some(DocumentFormat::PDF)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DocumentFormat::from_extension(Path::new("test.docx")),
|
|
||||||
Some(DocumentFormat::DOCX)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DocumentFormat::from_extension(Path::new("test.txt")),
|
|
||||||
Some(DocumentFormat::TXT)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DocumentFormat::from_extension(Path::new("test.md")),
|
|
||||||
Some(DocumentFormat::MD)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DocumentFormat::from_extension(Path::new("test.unknown")),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
|
|
||||||
|
|
||||||
fn test_text_cleaning() {
|
|
||||||
let processor = DocumentProcessor::default();
|
|
||||||
let dirty_text = " This is\n\n\na test\r\nwith multiple spaces ";
|
|
||||||
let cleaned = processor.clean_text(dirty_text);
|
|
||||||
assert_eq!(cleaned, "This is a test with multiple spaces");
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue