Update server components and keywords

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-23 15:52:35 -03:00
parent a491cc13a6
commit e2a5bf091a
126 changed files with 398 additions and 7455 deletions

View file

@ -141,7 +141,7 @@ askama = "0.12"
askama_axum = "0.4"
tracing-subscriber = { version = "0.3", features = ["fmt"] }
urlencoding = "2.1"
uuid = { version = "1.11", features = ["serde", "v4"] }
uuid = { version = "1.11", features = ["serde", "v4", "v5"] }
# === TLS/SECURITY DEPENDENCIES ===
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
@ -242,53 +242,5 @@ mockito = "1.7.0"
tempfile = "3"
# === SECURITY AND CODE QUALITY CONFIGURATION ===
[lints.rust]
unused_imports = "warn"
unused_variables = "warn"
unused_mut = "warn"
unsafe_code = "deny"
missing_debug_implementations = "warn"
[lints.clippy]
all = "warn"
pedantic = "warn"
nursery = "warn"
cargo = "warn"
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
todo = "warn"
# Disabled: has false positives for functions with mut self, heap types (Vec, String)
missing_const_for_fn = "allow"
# Disabled: transitive dependencies we cannot control
multiple_crate_versions = "allow"
# Disabled: many keyword functions need owned types for Rhai integration
needless_pass_by_value = "allow"
[profile.release]
lto = true
opt-level = "z"
strip = true
panic = "abort"
codegen-units = 1
overflow-checks = true
[profile.ci]
inherits = "release"
lto = false
codegen-units = 16
debug = false
[profile.low-memory]
inherits = "release"
lto = "thin"
codegen-units = 16
debug = false
incremental = false
opt-level = "s"
[profile.dev]
debug = 0
incremental = true
codegen-units = 256
opt-level = 0
[lints]
workspace = true

View file

@ -38,8 +38,13 @@ todo = "warn"
❌ NEVER leave dead code - DELETE it or IMPLEMENT it
❌ NEVER use approximate constants (3.14159) - use std::f64::consts::PI
❌ NEVER silence clippy in code - FIX THE CODE or configure in Cargo.toml
❌ NEVER add comments explaining what code does - code must be self-documenting
❌ NEVER use CDN links - all assets must be local
❌ NEVER run cargo check or cargo clippy - USE ONLY the diagnostics tool
❌ NEVER add comments - code must be self-documenting via types and naming
❌ NEVER add file header comments (//! or /*!) - no module docs
❌ NEVER add function doc comments (///) - types are the documentation
❌ NEVER add ASCII art or banners in code
❌ NEVER add TODO/FIXME/HACK comments - fix it or delete it
```
---
@ -177,6 +182,31 @@ pub fn calculate() -> i32 { }
pub fn calculate() -> i32 { }
```
### Zero Comments Policy
```rust
// ❌ WRONG - any comments
/// Returns the user's full name
fn get_full_name(&self) -> String { }
// Validate input before processing
fn process(data: &str) { }
//! This module handles user authentication
// ✅ CORRECT - self-documenting code, no comments
fn full_name(&self) -> String { }
fn process_validated_input(data: &str) { }
```
**Why zero comments:**
- Rust's type system documents intent (Result, Option, traits)
- Comments become stale when code changes
- LLMs can infer intent from well-structured code
- Good naming > comments
- Types are the documentation
### Const Functions
```rust
@ -368,6 +398,7 @@ src/shared/models.rs # Database models
## Remember
- **ZERO WARNINGS** - Every clippy warning must be fixed
- **ZERO COMMENTS** - No comments, no doc comments, no file headers, no ASCII art
- **NO ALLOW IN CODE** - Never use #[allow()] in source files
- **CARGO.TOML EXCEPTIONS OK** - Disable lints with false positives in Cargo.toml with comment
- **NO DEAD CODE** - Delete unused code, never prefix with _

View file

@ -417,7 +417,7 @@ fn get_bot_system_prompt(bot_id: Uuid, work_path: &str) -> String {
pub async fn generate_tips(
State(state): State<Arc<AppState>>,
Json(request): Json<TipRequest>,
) -> impl IntoResponse {
) -> (StatusCode, Json<TipResponse>) {
info!("Generating tips for session {}", request.session_id);
// Get session and bot info
@ -528,7 +528,7 @@ Provide tips for the attendant."#,
pub async fn polish_message(
State(state): State<Arc<AppState>>,
Json(request): Json<PolishRequest>,
) -> impl IntoResponse {
) -> (StatusCode, Json<PolishResponse>) {
info!("Polishing message for session {}", request.session_id);
let session_result = get_session(&state, request.session_id).await;
@ -626,7 +626,7 @@ Respond in JSON format:
pub async fn generate_smart_replies(
State(state): State<Arc<AppState>>,
Json(request): Json<SmartRepliesRequest>,
) -> impl IntoResponse {
) -> (StatusCode, Json<SmartRepliesResponse>) {
info!(
"Generating smart replies for session {}",
request.session_id
@ -730,7 +730,7 @@ Generate 3 reply options for the attendant."#,
pub async fn generate_summary(
State(state): State<Arc<AppState>>,
Path(session_id): Path<Uuid>,
) -> impl IntoResponse {
) -> (StatusCode, Json<SummaryResponse>) {
info!("Generating summary for session {}", session_id);
let session_result = get_session(&state, session_id).await;
@ -1133,13 +1133,13 @@ async fn handle_take_command(
.and_then(|v| v.as_str())
.unwrap_or("Unknown");
Ok(format!(
Ok::<String, String>(format!(
" *Conversation assigned*\n\nCustomer: *{}*\nSession: {}\n\nYou can now respond to this customer. Their messages will be forwarded to you.",
name,
&session.id.to_string()[..8]
))
} else {
Ok(" No conversations waiting in queue.".to_string())
Ok::<String, String>(" No conversations waiting in queue.".to_string())
}
})
.await
@ -1196,6 +1196,7 @@ async fn handle_status_command(
.load(&mut db_conn)
.map_err(|e| e.to_string())?;
let session_count = sessions.len();
for session in sessions {
let mut ctx = session.context_data.clone();
ctx["attendant_status"] = serde_json::json!(status_val);
@ -1207,7 +1208,7 @@ async fn handle_status_command(
.map_err(|e| e.to_string())?;
}
Ok::<usize, String>(sessions.len())
Ok::<usize, String>(session_count)
})
.await
.map_err(|e| e.to_string())?;
@ -1369,8 +1370,7 @@ async fn handle_tips_command(
};
// Generate tips
let response = generate_tips(State(state.clone()), Json(request)).await;
let (_, Json(tip_response)) = response.into_response().into_parts();
let (_, Json(tip_response)) = generate_tips(State(state.clone()), Json(request)).await;
if tip_response.tips.is_empty() {
return Ok(" No specific tips for this conversation yet.".to_string());
@ -1410,8 +1410,7 @@ async fn handle_polish_command(
tone: "professional".to_string(),
};
let response = polish_message(State(state.clone()), Json(request)).await;
let (_, Json(polish_response)) = response.into_response().into_parts();
let (_, Json(polish_response)) = polish_message(State(state.clone()), Json(request)).await;
if !polish_response.success {
return Err(polish_response
@ -1447,8 +1446,7 @@ async fn handle_replies_command(
history,
};
let response = generate_smart_replies(State(state.clone()), Json(request)).await;
let (_, Json(replies_response)) = response.into_response().into_parts();
let (_, Json(replies_response)) = generate_smart_replies(State(state.clone()), Json(request)).await;
if replies_response.replies.is_empty() {
return Ok(" No reply suggestions available.".to_string());
@ -1476,8 +1474,7 @@ async fn handle_summary_command(
) -> Result<String, String> {
let session_id = current_session.ok_or("No active conversation")?;
let response = generate_summary(State(state.clone()), Path(session_id)).await;
let (_, Json(summary_response)) = response.into_response().into_parts();
let (_, Json(summary_response)) = generate_summary(State(state.clone()), Path(session_id)).await;
if !summary_response.success {
return Err(summary_response
@ -2102,76 +2099,3 @@ fn analyze_sentiment_keywords(message: &str) -> SentimentAnalysis {
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -661,28 +661,3 @@ async fn handle_attendant_message(
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_exports() {
// Test that types are properly exported
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?");
}
}

View file

@ -379,126 +379,3 @@ fn transform_line(line: &str) -> String {
// Not a GOTO line, return as-is
trimmed.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[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")); // This is special, not regular GOTO
}
#[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); // Unchanged
}
#[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"));
}
}

View file

@ -839,107 +839,3 @@ pub async fn respond_to_a2a_message(
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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));
}
}

View file

@ -867,72 +867,3 @@ struct BotConfigRow {
#[diesel(sql_type = diesel::sql_types::Nullable<diesel::sql_types::Text>)]
model_config: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -483,34 +483,3 @@ async fn create_team_channel(
trace!("Created communication channel for team {}", team_name);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -297,62 +297,3 @@ fn add_tool_suggestion(
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -1079,118 +1079,3 @@ async fn set_reflection_enabled(
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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")); // insights count
assert!(summary.contains("1")); // improvements count
}
}

View file

@ -92,76 +92,3 @@ fn items_equal(a: &Dynamic, b: &Dynamic) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[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")
));
}
}

View file

@ -293,42 +293,3 @@ fn register_utility_functions(engine: &mut Engine) {
debug!("Registered array utility functions");
}
#[cfg(test)]
mod tests {
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() {
// Test flattening logic
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]);
}
}

View file

@ -100,48 +100,3 @@ pub fn unshift_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut
debug!("Registered UNSHIFT keyword");
}
#[cfg(test)]
mod tests {
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);
}
}

View file

@ -138,67 +138,3 @@ fn slice_array(arr: &Array, start: i64, end: Option<i64>) -> Array {
arr[start_idx..end_idx].to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
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());
}
}

View file

@ -88,61 +88,3 @@ fn to_f64(value: &Dynamic) -> Option<f64> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -52,92 +52,3 @@ fn unique_array(arr: Array) -> Array {
result
}
#[cfg(test)]
mod tests {
use super::*;
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);
// "1" (int) and "1" (string) may have same string representation
// so behavior depends on Dynamic::to_string() implementation
assert!(result.len() >= 1 && result.len() <= 2);
}
}

View file

@ -615,41 +615,3 @@ async fn send_meeting_invite(
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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));
}
}

View file

@ -485,36 +485,3 @@ pub fn register_card_keyword(runtime: &mut BasicRuntime, llm_provider: Arc<dyn L
})
});
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -190,23 +190,3 @@ pub fn get_active_kb_count(
Ok(result.count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clear_kb_syntax() {
let mut engine = Engine::new();
// Test CLEAR_KB with argument
assert!(engine
.register_custom_syntax(&["CLEAR_KB", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
.is_ok());
// Test CLEAR_KB without argument
assert!(engine
.register_custom_syntax(&["CLEAR_KB"], true, |_, _| Ok(Dynamic::UNIT))
.is_ok());
}
}

View file

@ -918,85 +918,3 @@ lxc.mount.entry = tmpfs tmp tmpfs defaults 0 0
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -124,13 +124,3 @@ pub fn register_core_functions(state: Arc<AppState>, user: UserSession, engine:
debug!("All core BASIC functions registered successfully");
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_structure() {
// This test verifies the module compiles correctly
// Actual function tests are in their respective submodules
assert!(true);
}
}

View file

@ -440,28 +440,3 @@ async fn send_task_notification(
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -155,7 +155,7 @@ fn register_get_queue(state: Arc<AppState>, _user: UserSession, engine: &mut Eng
.unwrap();
}
fn get_queue_impl(state: &Arc<AppState>, filter: Option<String>) -> Dynamic {
pub fn get_queue_impl(state: &Arc<AppState>, filter: Option<String>) -> Dynamic {
let conn = state.conn.clone();
let result = std::thread::spawn(move || {
@ -314,7 +314,7 @@ fn register_next_in_queue(state: Arc<AppState>, _user: UserSession, engine: &mut
});
}
fn next_in_queue_impl(state: &Arc<AppState>) -> Dynamic {
pub fn next_in_queue_impl(state: &Arc<AppState>) -> Dynamic {
let conn = state.conn.clone();
let result = std::thread::spawn(move || {
@ -428,7 +428,7 @@ fn register_assign_conversation(state: Arc<AppState>, _user: UserSession, engine
);
}
fn assign_conversation_impl(
pub fn assign_conversation_impl(
state: &Arc<AppState>,
session_id: &str,
attendant_id: &str,
@ -525,7 +525,7 @@ fn register_resolve_conversation(state: Arc<AppState>, _user: UserSession, engin
});
}
fn resolve_conversation_impl(
pub fn resolve_conversation_impl(
state: &Arc<AppState>,
session_id: &str,
reason: Option<String>,
@ -614,7 +614,7 @@ fn register_set_priority(state: Arc<AppState>, _user: UserSession, engine: &mut
);
}
fn set_priority_impl(state: &Arc<AppState>, session_id: &str, priority: Dynamic) -> Dynamic {
pub fn set_priority_impl(state: &Arc<AppState>, session_id: &str, priority: Dynamic) -> Dynamic {
let conn = state.conn.clone();
let session_uuid = match Uuid::parse_str(session_id) {
Ok(u) => u,
@ -711,7 +711,7 @@ fn register_get_attendants(state: Arc<AppState>, _user: UserSession, engine: &mu
});
}
fn get_attendants_impl(_state: &Arc<AppState>, status_filter: Option<String>) -> Dynamic {
pub fn get_attendants_impl(_state: &Arc<AppState>, status_filter: Option<String>) -> Dynamic {
// Read from attendant.csv
let work_path = std::env::var("WORK_PATH").unwrap_or_else(|_| "./work".to_string());
@ -841,7 +841,7 @@ fn register_get_attendant_stats(state: Arc<AppState>, _user: UserSession, engine
.unwrap();
}
fn get_attendant_stats_impl(state: &Arc<AppState>, attendant_id: &str) -> Dynamic {
pub fn get_attendant_stats_impl(state: &Arc<AppState>, attendant_id: &str) -> Dynamic {
let conn = state.conn.clone();
let att_id = attendant_id.to_string();
@ -935,11 +935,11 @@ fn register_get_tips(state: Arc<AppState>, _user: UserSession, engine: &mut Engi
);
}
fn get_tips_impl(_state: &Arc<AppState>, _session_id: &str, message: &str) -> Dynamic {
pub fn get_tips_impl(_state: &Arc<AppState>, _session_id: &str, message: &str) -> Dynamic {
create_fallback_tips(message)
}
fn create_fallback_tips(message: &str) -> Dynamic {
pub fn create_fallback_tips(message: &str) -> Dynamic {
let msg_lower = message.to_lowercase();
let mut tips = Vec::new();
@ -1043,7 +1043,7 @@ fn register_polish_message(state: Arc<AppState>, _user: UserSession, engine: &mu
});
}
fn polish_message_impl(_state: &Arc<AppState>, message: &str, _tone: &str) -> Dynamic {
pub fn polish_message_impl(_state: &Arc<AppState>, message: &str, _tone: &str) -> Dynamic {
// Simple polishing without LLM (fallback)
let mut polished = message.to_string();
@ -1106,7 +1106,7 @@ fn register_get_smart_replies(state: Arc<AppState>, _user: UserSession, engine:
});
}
fn get_smart_replies_impl(_state: &Arc<AppState>, _session_id: &str) -> Dynamic {
pub fn get_smart_replies_impl(_state: &Arc<AppState>, _session_id: &str) -> Dynamic {
// Return default replies (fallback without LLM)
let mut replies = Vec::new();
@ -1170,7 +1170,7 @@ fn register_get_summary(state: Arc<AppState>, _user: UserSession, engine: &mut E
});
}
fn get_summary_impl(state: &Arc<AppState>, session_id: &str) -> Dynamic {
pub fn get_summary_impl(state: &Arc<AppState>, session_id: &str) -> Dynamic {
let conn = state.conn.clone();
let session_uuid = match Uuid::parse_str(session_id) {
Ok(u) => u,
@ -1242,7 +1242,7 @@ fn register_analyze_sentiment(state: Arc<AppState>, _user: UserSession, engine:
);
}
fn analyze_sentiment_impl(_state: &Arc<AppState>, _session_id: &str, message: &str) -> Dynamic {
pub fn analyze_sentiment_impl(_state: &Arc<AppState>, _session_id: &str, message: &str) -> Dynamic {
// Keyword-based sentiment analysis (fallback without LLM)
let msg_lower = message.to_lowercase();
@ -1355,7 +1355,7 @@ fn register_tag_conversation(state: Arc<AppState>, _user: UserSession, engine: &
);
}
fn tag_conversation_impl(state: &Arc<AppState>, session_id: &str, tags: Vec<String>) -> Dynamic {
pub fn tag_conversation_impl(state: &Arc<AppState>, session_id: &str, tags: Vec<String>) -> Dynamic {
let conn = state.conn.clone();
let session_uuid = match Uuid::parse_str(session_id) {
Ok(u) => u,
@ -1453,7 +1453,7 @@ fn register_add_note(state: Arc<AppState>, _user: UserSession, engine: &mut Engi
});
}
fn add_note_impl(
pub fn add_note_impl(
state: &Arc<AppState>,
session_id: &str,
note: &str,
@ -1547,7 +1547,7 @@ fn register_get_customer_history(state: Arc<AppState>, _user: UserSession, engin
});
}
fn get_customer_history_impl(state: &Arc<AppState>, user_id: &str) -> Dynamic {
pub fn get_customer_history_impl(state: &Arc<AppState>, user_id: &str) -> Dynamic {
let conn = state.conn.clone();
let user_uuid = match Uuid::parse_str(user_id) {
Ok(u) => u,
@ -1614,7 +1614,7 @@ fn get_customer_history_impl(state: &Arc<AppState>, user_id: &str) -> Dynamic {
// Helper Functions
fn create_error_result(message: &str) -> Dynamic {
pub fn create_error_result(message: &str) -> Dynamic {
let mut result = Map::new();
result.insert("success".into(), Dynamic::from(false));
result.insert("error".into(), Dynamic::from(message.to_string()));
@ -1622,137 +1622,3 @@ fn create_error_result(message: &str) -> Dynamic {
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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") == false);
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(),
]
}
}

View file

@ -674,57 +674,3 @@ fn update_lead_score_in_db(state: &Arc<AppState>, lead_id: &str, score: i64) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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); // At least CEO bonus
}
#[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
);
}
}

View file

@ -1078,45 +1078,3 @@ fn sanitize_identifier(name: &str) -> String {
fn sanitize_sql(value: &str) -> String {
value.replace('\'', "''")
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -56,7 +56,7 @@ fn parse_datetime(datetime_str: &str) -> Option<NaiveDateTime> {
.or_else(|| parse_date(trimmed).and_then(|d| d.and_hms_opt(0, 0, 0)))
}
fn dateadd_impl(date_str: &str, amount: i64, unit: &str) -> String {
pub fn dateadd_impl(date_str: &str, amount: i64, unit: &str) -> String {
let unit_lower = unit.to_lowercase();
if let Some(datetime) = parse_datetime(date_str) {
@ -104,7 +104,7 @@ fn dateadd_impl(date_str: &str, amount: i64, unit: &str) -> String {
}
}
fn datediff_impl(date1: &str, date2: &str, unit: &str) -> i64 {
pub fn datediff_impl(date1: &str, date2: &str, unit: &str) -> i64 {
let unit_lower = unit.to_lowercase();
if let (Some(dt1), Some(dt2)) = (parse_datetime(date1), parse_datetime(date2)) {
@ -128,48 +128,3 @@ fn datediff_impl(date1: &str, date2: &str, unit: &str) -> i64 {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -142,7 +142,7 @@ pub fn isdate_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut E
debug!("Registered ISDATE keyword");
}
fn format_date_impl(date_str: &str, format: &str) -> String {
pub fn format_date_impl(date_str: &str, format: &str) -> String {
if let Some(datetime) = parse_datetime(date_str) {
let chrono_format = format
.replace("YYYY", "%Y")
@ -166,34 +166,3 @@ fn format_date_impl(date_str: &str, format: &str) -> String {
date_str.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -277,62 +277,3 @@ pub fn timestamp_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mu
debug!("Registered TIMESTAMP keyword");
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -675,117 +675,3 @@ pub mod sql {
)
"#;
}
#[cfg(test)]
mod tests {
use super::*;
#[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() {
// Test with code block
let response = "Here's the summary:\n```json\n{\"summary\": \"test\"}\n```\n";
assert!(extract_json(response).is_ok());
// Test with raw JSON
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>());
}
}

View file

@ -187,18 +187,3 @@ pub fn log_error_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mu
debug!("Registered LOG_ERROR keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -241,67 +241,3 @@ pub fn handle_string_error(error_msg: &str) -> Result<Dynamic, Box<EvalAltResult
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_resume_next_flag() {
// Initially should be false
assert!(!is_error_resume_next_active());
// Enable it
set_error_resume_next(true);
assert!(is_error_resume_next_active());
// Disable it
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);
// Should return error when ON ERROR RESUME NEXT is not active
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);
// Should return Ok(UNIT) when ON ERROR RESUME NEXT is active
assert!(handled.is_ok());
assert_eq!(get_last_error(), Some("Test error".to_string()));
// Cleanup
set_error_resume_next(false);
}
}

View file

@ -28,12 +28,3 @@
// - Stack trace capture
// - Error context/metadata
// - Retry mechanisms
#[cfg(test)]
mod tests {
#[test]
fn test_placeholder() {
// Placeholder test - actual functionality is in mod.rs
assert!(true);
}
}

View file

@ -1772,23 +1772,3 @@ fn dynamic_to_file_data(value: &Dynamic) -> FileData {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -1545,75 +1545,3 @@ async fn process_video_description(
Err("Failed to process video".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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); // Partial match
assert!(!validate_menu("Orange", &options).is_valid);
}
#[test]
fn test_validate_credit_card() {
// Valid Visa test number
assert!(validate_credit_card("4111 1111 1111 1111").is_valid);
// Invalid (fails Luhn)
assert!(!validate_credit_card("1234567890123456").is_valid);
}
}

View file

@ -816,38 +816,3 @@ fn json_to_dynamic(value: &Value) -> Dynamic {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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", &params);
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());
}
}

View file

@ -862,151 +862,3 @@ pub mod sql {
WHERE bot_id = $1 AND name = $2
"#;
}
#[cfg(test)]
mod tests {
use super::*;
#[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), // 1 second timeout
None,
);
assert!(!manager.is_expired(&request));
// Manually set expired time
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>());
}
}

View file

@ -682,43 +682,3 @@ fn dynamic_to_json(data: &Dynamic) -> Value {
Value::String(data.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -425,44 +425,3 @@ async fn get_storage_size(
let stats = get_kb_statistics(state, user).await?;
Ok(stats.total_disk_size_mb)
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -835,96 +835,3 @@ pub mod entity_types {
pub const SKILL: &str = "skill";
pub const TECHNOLOGY: &str = "technology";
}
#[cfg(test)]
mod tests {
use super::*;
#[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>());
}
}

View file

@ -87,13 +87,3 @@ pub fn register_lead_scoring_keywords(
debug!("Lead scoring keywords registered successfully");
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_structure() {
// This test verifies the module compiles correctly
// Actual function tests are in the crm/score_lead.rs module
assert!(true);
}
}

View file

@ -442,33 +442,3 @@ fn dynamic_to_json(data: &Dynamic) -> serde_json::Value {
serde_json::Value::String(data.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -12,24 +12,3 @@ pub fn abs_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered ABS keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -94,37 +94,3 @@ pub fn avg_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered AVG/AVERAGE keyword");
}
#[cfg(test)]
mod tests {
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);
}
}

View file

@ -105,46 +105,3 @@ pub fn pow_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered POW keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -57,18 +57,3 @@ pub fn min_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered MIN keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -83,13 +83,3 @@ pub fn mod_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered MOD keyword");
}
#[cfg(test)]
mod tests {
#[test]
fn test_mod() {
assert_eq!(17 % 5, 2);
assert_eq!(10 % 3, 1);
assert_eq!(0 % 5, 0);
}
}

View file

@ -18,22 +18,3 @@ pub fn round_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut En
debug!("Registered ROUND keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -54,32 +54,3 @@ pub fn pi_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engin
debug!("Registered PI keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -928,51 +928,3 @@ pub fn generate_example_configs() -> Vec<McpServerConfig> {
pub type McpDirectoryScanner = McpCsvLoader;
pub type McpDirectoryScanResult = McpLoadResult;
pub type McpScanError = McpLoadError;
#[cfg(test)]
mod tests {
use super::*;
#[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
));
}
}

View file

@ -511,69 +511,3 @@ fn generate_message_id() -> String {
format!("msg_{}", timestamp)
}
#[cfg(test)]
mod tests {
use super::*;
#[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_"));
}
}

View file

@ -552,83 +552,3 @@ pub fn get_session_routing_strategy(state: &AppState, session_id: Uuid) -> Routi
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -498,212 +498,3 @@ pub async fn process_folder_event(
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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
);
}
}

View file

@ -398,201 +398,3 @@ pub fn sanitize_email_for_filename(email: &str) -> String {
.collect::<String>()
.to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -701,99 +701,3 @@ async fn send_player_command(
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -673,165 +673,3 @@ pub fn get_procedure(name: &str) -> Option<ProcedureDefinition> {
}
// TESTS
#[cfg(test)]
mod tests {
use super::*;
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);
// SUB should be extracted
assert!(!result.contains("SUB MySub"));
assert!(!result.contains("END SUB"));
assert!(result.contains("x = 1"));
assert!(result.contains("y = 2"));
// Procedure should be registered
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);
// FUNCTION should be extracted
assert!(!result.contains("FUNCTION Add"));
assert!(!result.contains("END FUNCTION"));
assert!(result.contains("result = Add(1, 2)"));
// Procedure should be registered
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();
// First register a SUB
let sub_input = r#"
SUB Greet(name)
TALK "Hello " + name
END SUB
"#;
preprocess_subs(sub_input);
// Then preprocess CALL
let call_input = "CALL Greet(\"World\")";
let result = preprocess_calls(call_input);
// Should contain parameter assignment and body
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);
// Should have inlined the CALL
assert!(result.contains("let name = \"User\""));
assert!(result.contains("let greeting = \"Hello\""));
// Original definitions should be gone
assert!(!result.contains("SUB SendGreeting"));
assert!(!result.contains("END SUB"));
assert!(!result.contains("FUNCTION Calculate"));
assert!(!result.contains("END FUNCTION"));
// Both should be registered
assert!(has_procedure("SENDGREETING"));
assert!(has_procedure("CALCULATE"));
}
}

View file

@ -429,43 +429,3 @@ pub fn generate_qr_code_with_logo(
Ok(output_path.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_code_generation() {
// Create a mock state and user for testing
// In real tests, you'd set up proper test fixtures
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());
}
}

View file

@ -329,18 +329,3 @@ struct MemoryRecord {
#[diesel(sql_type = diesel::sql_types::Jsonb)]
value: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_duration() {
// Test various duration formats
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());
}
}

View file

@ -463,25 +463,3 @@ async fn save_to_table(
Ok(record_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -592,28 +592,3 @@ fn extract_template_subject(content: &str) -> Option<String> {
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[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()));
}
}

View file

@ -112,13 +112,3 @@ pub fn register_send_template_keywords(
debug!("Send template keywords registered successfully");
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_structure() {
// This test verifies the module compiles correctly
// Actual function tests are in the messaging/send_template.rs module
assert!(true);
}
}

View file

@ -421,171 +421,3 @@ pub fn execute_set_schedule(
"rows_affected": result
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[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()); // > 59
}
}

View file

@ -924,46 +924,3 @@ async fn send_via_custom_webhook(
Err(format!("Custom webhook error: {}", error_text).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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())
);
}
}

View file

@ -460,23 +460,3 @@ async fn fetch_twitter_metrics(
..Default::default()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -170,20 +170,3 @@ async fn save_scheduled_post(
);
Ok(post_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -104,13 +104,3 @@ pub fn register_social_media_keywords(
debug!("Social media keywords registered successfully");
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_structure() {
// This test verifies the module compiles correctly
// Actual function tests are in the social/ module files
assert!(true);
}
}

View file

@ -50,7 +50,7 @@ pub fn instr_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut En
///
/// # Returns
/// * 1-based position of the first occurrence, or 0 if not found
fn instr_impl(start: i64, haystack: &str, needle: &str) -> i64 {
pub fn instr_impl(start: i64, haystack: &str, needle: &str) -> i64 {
// Handle edge cases
if haystack.is_empty() || needle.is_empty() {
return 0;
@ -120,7 +120,7 @@ pub fn is_numeric_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &m
/// # Returns
/// * `true` if the value can be parsed as a number
/// * `false` otherwise
fn is_numeric_impl(value: &str) -> bool {
pub fn is_numeric_impl(value: &str) -> bool {
let trimmed = value.trim();
// Empty string is not numeric
@ -310,61 +310,3 @@ pub fn register_string_functions(state: Arc<AppState>, user: UserSession, engine
mid_keyword(&state, user.clone(), engine);
replace_keyword(&state, user, engine);
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -70,7 +70,7 @@ pub fn switch_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut E
///
/// Compares two Dynamic values for equality, handling type coercion
/// where appropriate.
fn switch_match_impl(expr: &Dynamic, case_val: &Dynamic) -> bool {
pub fn switch_match_impl(expr: &Dynamic, case_val: &Dynamic) -> bool {
// Try string comparison first
if let (Some(e), Some(c)) = (
expr.clone().into_string().ok(),
@ -219,78 +219,3 @@ pub fn preprocess_switch(input: &str) -> String {
result
}
#[cfg(test)]
mod tests {
use super::*;
#[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("||"));
}
}

View file

@ -628,136 +628,3 @@ pub fn register_table_keywords(
// This function exists for consistency with other keyword modules
trace!("TABLE keyword registered (compile-time only)");
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -794,94 +794,3 @@ pub fn get_tool_definition() -> serde_json::Value {
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[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,
},
];
// Find by exact name
let found = find_attendant(&attendants, Some("John Smith"), None);
assert!(found.is_some());
assert_eq!(found.unwrap().id, "att-001");
// Find by partial name
let found = find_attendant(&attendants, Some("john"), None);
assert!(found.is_some());
assert_eq!(found.unwrap().id, "att-001");
// Find by alias
let found = find_attendant(&attendants, Some("johnny"), None);
assert!(found.is_some());
assert_eq!(found.unwrap().id, "att-001");
// Find by department
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"
);
}
}

View file

@ -224,30 +224,3 @@ pub struct AccountCredentials {
pub access_token: String,
pub refresh_token: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -194,18 +194,3 @@ pub fn get_active_kbs_for_session(
.map(|r| (r.kb_name, r.kb_folder_path, r.qdrant_collection))
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_use_kb_syntax() {
let mut engine = Engine::new();
// This would normally use real state and session
// For now just test that the syntax can be registered
assert!(engine
.register_custom_syntax(&["USE_KB", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
.is_ok());
}
}

View file

@ -377,35 +377,3 @@ fn sanitize_url_for_collection(url: &str) -> String {
.collect::<String>()
.to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[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();
// Test USE_WEBSITE with argument
assert!(engine
.register_custom_syntax(&["USE_WEBSITE", "$expr$"], true, |_, _| Ok(Dynamic::UNIT))
.is_ok());
// Test CLEAR_WEBSITES without argument
assert!(engine
.register_custom_syntax(&["CLEAR_WEBSITES"], true, |_, _| Ok(Dynamic::UNIT))
.is_ok());
}
}

View file

@ -292,15 +292,3 @@ async fn clear_user_memory_async(state: &AppState, user_id: Uuid) -> Result<(),
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -77,59 +77,3 @@ fn check_empty(value: &Dynamic) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
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));
}
}

View file

@ -15,27 +15,3 @@ pub fn isnull_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut E
debug!("Registered ISNULL keyword");
}
#[cfg(test)]
mod tests {
#[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());
}
}

View file

@ -138,44 +138,3 @@ pub fn iif_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engi
debug!("Registered IIF/IF_FUNC/CHOOSE/SWITCH_FUNC keywords");
}
#[cfg(test)]
mod tests {
#[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");
}
}

View file

@ -129,21 +129,3 @@ pub fn cdbl_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Eng
debug!("Registered CDBL keyword");
}
#[cfg(test)]
mod tests {
#[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);
}
}

View file

@ -135,27 +135,3 @@ fn is_numeric(value: &Dynamic) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[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)));
}
}

View file

@ -400,41 +400,3 @@ fn get_weather_api_key(_state: &AppState) -> Result<String, String> {
// For now, return error indicating configuration needed
Err("Weather API key not configured. Please set 'weather-api-key' in config.csv".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -431,73 +431,3 @@ fn dynamic_to_json(value: &Dynamic) -> Value {
Value::String(value.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -557,50 +557,3 @@ pub fn configure_calendar_routes() -> Router<Arc<AppState>> {
.route("/ui/calendar/event/new", get(new_event_form))
.route("/ui/calendar/new", get(new_calendar_form))
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
}
}

View file

@ -270,35 +270,42 @@ impl AccessReviewService {
modified: Vec<(Uuid, AccessLevel)>,
comments: String,
) -> Result<AccessReviewResult> {
let review = self
.reviews
.get_mut(&review_id)
.ok_or_else(|| anyhow!("Review not found"))?;
// Get review info first
let (reviewer_id, user_id) = {
let review = self
.reviews
.get(&review_id)
.ok_or_else(|| anyhow!("Review not found"))?;
if review.status != ReviewStatus::Pending && review.status != ReviewStatus::InProgress {
return Err(anyhow!("Review already completed"));
}
if review.status != ReviewStatus::Pending && review.status != ReviewStatus::InProgress {
return Err(anyhow!("Review already completed"));
}
(review.reviewer_id, review.user_id)
};
// Process revocations
for perm_id in &revoked {
self.revoke_permission(*perm_id, review.reviewer_id)?;
self.revoke_permission(*perm_id, reviewer_id)?;
}
// Process modifications
for (perm_id, new_level) in &modified {
if let Some(permissions) = self.permissions.get_mut(&review.user_id) {
if let Some(permissions) = self.permissions.get_mut(&user_id) {
if let Some(perm) = permissions.iter_mut().find(|p| p.id == *perm_id) {
perm.access_level = new_level.clone();
}
}
}
review.status = ReviewStatus::Approved;
review.comments = Some(comments.clone());
// Update review status
if let Some(review) = self.reviews.get_mut(&review_id) {
review.status = ReviewStatus::Approved;
review.comments = Some(comments.clone());
}
let result = AccessReviewResult {
review_id,
reviewer_id: review.reviewer_id,
reviewer_id,
reviewed_at: Utc::now(),
approved_permissions: approved,
revoked_permissions: revoked,

View file

@ -658,83 +658,3 @@ fn escape_csv(s: &str) -> String {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pattern_matching() {
let scanner = CodeScanner::new("/tmp/test");
// Test password detection
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'"#));
// Test underscore keyword detection
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"));
}
}

View file

@ -434,44 +434,3 @@ pub struct ComplianceReport {
pub low_issues: usize,
pub results: Vec<ComplianceCheckResult>,
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}

View file

@ -350,14 +350,18 @@ impl PolicyChecker {
/// Check all active policies
pub fn check_all_policies(&mut self, context: &PolicyContext) -> Vec<PolicyCheckResult> {
// Collect active policy IDs first to avoid borrow issues
let active_policy_ids: Vec<Uuid> = self
.policies
.iter()
.filter(|(_, p)| p.status == PolicyStatus::Active)
.map(|(id, _)| *id)
.collect();
let mut results = Vec::new();
for policy in self.policies.values() {
if policy.status != PolicyStatus::Active {
continue;
}
let result = self.check_policy(policy.id, context);
for policy_id in active_policy_ids {
let result = self.check_policy(policy_id, context);
if let Ok(result) = result {
results.push(result);
}

View file

@ -258,43 +258,56 @@ impl TrainingTracker {
attempt_id: Uuid,
score: u32,
) -> Result<bool> {
// Get course info first
let (course_id, passing_score, validity_days, max_attempts, course_title) = {
let assignment = self
.assignments
.get(&assignment_id)
.ok_or_else(|| anyhow!("Assignment not found"))?;
let course = self
.courses
.get(&assignment.course_id)
.ok_or_else(|| anyhow!("Course not found"))?;
(course.id, course.passing_score, course.validity_days, course.max_attempts, course.title.clone())
};
let assignment = self
.assignments
.get_mut(&assignment_id)
.ok_or_else(|| anyhow!("Assignment not found"))?;
let course = self
.courses
.get(&assignment.course_id)
.ok_or_else(|| anyhow!("Course not found"))?
.clone();
let attempt = assignment
let attempt_idx = assignment
.attempts
.iter_mut()
.find(|a| a.id == attempt_id)
.iter()
.position(|a| a.id == attempt_id)
.ok_or_else(|| anyhow!("Attempt not found"))?;
let end_time = Utc::now();
let time_spent = (end_time - attempt.start_time).num_minutes() as u32;
let start_time = assignment.attempts[attempt_idx].start_time;
let time_spent = (end_time - start_time).num_minutes() as u32;
let passed = score >= passing_score;
attempt.end_time = Some(end_time);
attempt.score = Some(score);
attempt.time_spent_minutes = Some(time_spent);
attempt.passed = score >= course.passing_score;
// Update attempt
assignment.attempts[attempt_idx].end_time = Some(end_time);
assignment.attempts[attempt_idx].score = Some(score);
assignment.attempts[attempt_idx].time_spent_minutes = Some(time_spent);
assignment.attempts[attempt_idx].passed = passed;
if attempt.passed {
let user_id = assignment.user_id;
let attempts_count = assignment.attempts.len();
if passed {
assignment.status = TrainingStatus::Completed;
assignment.completion_date = Some(end_time);
assignment.expiry_date = Some(end_time + Duration::days(course.validity_days));
assignment.expiry_date = Some(end_time + Duration::days(validity_days));
// Issue certificate
let certificate = TrainingCertificate {
id: Uuid::new_v4(),
user_id: assignment.user_id,
course_id: course.id,
user_id,
course_id,
issued_date: end_time,
expiry_date: end_time + Duration::days(course.validity_days),
expiry_date: end_time + Duration::days(validity_days),
certificate_number: format!(
"CERT-{}",
Uuid::new_v4().to_string()[..8].to_uppercase()
@ -306,15 +319,15 @@ impl TrainingTracker {
log::info!(
"User {} completed training '{}' with score {}",
assignment.user_id,
course.title,
user_id,
course_title,
score
);
} else if assignment.attempts.len() >= course.max_attempts as usize {
} else if attempts_count >= max_attempts as usize {
assignment.status = TrainingStatus::Failed;
}
Ok(attempt.passed)
Ok(passed)
}
/// Get user compliance status

View file

@ -939,31 +939,3 @@ pub fn apply_wizard_config(config: &WizardConfig) -> io::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -1016,58 +1016,3 @@ impl From<&BotConfig> for BotRoute {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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()));
}
}

View file

@ -279,90 +279,3 @@ pub enum TaskType {
/// Default/unknown task type
Default,
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
}
}

View file

@ -115,46 +115,3 @@ impl SseConfig {
std::time::Duration::from_secs(self.heartbeat_seconds as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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)
);
}
}

View file

@ -130,68 +130,3 @@ impl UserMemoryConfig {
self.default_ttl > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}

View file

@ -507,65 +507,3 @@ impl DocumentProcessor {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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"));
// Verify chunks are created
assert!(!chunks.is_empty());
// Verify chunk size
for chunk in &chunks {
assert!(chunk.content.len() <= processor.chunk_size);
}
// Verify overlap exists
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())];
// There should be some overlap
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");
}
}

View file

@ -363,29 +363,3 @@ impl EmailLike for SimpleEmail {
&self.body
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dimension_detection() {
assert_eq!(EmbeddingConfig::detect_dimensions("bge-small-en"), 384);
assert_eq!(EmbeddingConfig::detect_dimensions("all-mpnet-base-v2"), 768);
assert_eq!(
EmbeddingConfig::detect_dimensions("text-embedding-ada-002"),
1536
);
assert_eq!(EmbeddingConfig::detect_dimensions("unknown-model"), 384);
}
#[tokio::test]
async fn test_text_cleaning_for_embedding() {
let text = "This is a test\n\nWith multiple lines";
let _generator = EmbeddingGenerator::new("http://localhost:8082".to_string());
// This would test actual embedding generation if service is available
// For unit tests, we just verify the structure is correct
assert!(!text.is_empty());
}
}

View file

@ -564,49 +564,3 @@ impl KbFolderMonitor {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collection_name_generation() {
let bot_name = "mybot";
let kb_name = "docs";
let collection_name = format!("{}_{}", bot_name, kb_name);
assert_eq!(collection_name, "mybot_docs");
}
#[test]
fn test_qdrant_point_creation() {
let chunk = TextChunk {
content: "Test content".to_string(),
metadata: super::super::document_processor::ChunkMetadata {
document_path: "test.txt".to_string(),
document_title: Some("Test".to_string()),
chunk_index: 0,
total_chunks: 1,
start_char: 0,
end_char: 12,
page_number: None,
},
};
let embedding = Embedding {
vector: vec![0.1, 0.2, 0.3],
dimensions: 3,
model: "test".to_string(),
tokens_used: None,
};
let indexer = KbIndexer::new(EmbeddingConfig::default(), QdrantConfig::default());
let points = indexer
.create_qdrant_points("test.txt", vec![(chunk, embedding)])
.unwrap();
assert_eq!(points.len(), 1);
assert_eq!(points[0].vector.len(), 3);
assert!(points[0].payload.contains_key("content"));
}
}

View file

@ -205,27 +205,3 @@ pub enum ChangeType {
Modified,
Deleted,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_kb_manager_creation() {
let temp_dir = TempDir::new().unwrap();
let manager = KnowledgeBaseManager::new(temp_dir.path());
// Test that manager is created successfully
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");
}
}

View file

@ -241,52 +241,3 @@ impl OAuthState {
serde_json::from_str(&json).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provider_from_str() {
assert_eq!(
OAuthProvider::from_str("google"),
Some(OAuthProvider::Google)
);
assert_eq!(
OAuthProvider::from_str("DISCORD"),
Some(OAuthProvider::Discord)
);
assert_eq!(
OAuthProvider::from_str("Twitter"),
Some(OAuthProvider::Twitter)
);
assert_eq!(OAuthProvider::from_str("x"), Some(OAuthProvider::Twitter));
assert_eq!(OAuthProvider::from_str("invalid"), None);
}
#[test]
fn test_oauth_state_encode_decode() {
let state = OAuthState::new(OAuthProvider::Google, Some("/dashboard".to_string()));
let encoded = state.encode();
let decoded = OAuthState::decode(&encoded).unwrap();
assert_eq!(decoded.provider, OAuthProvider::Google);
assert_eq!(decoded.redirect_after, Some("/dashboard".to_string()));
assert!(!decoded.is_expired());
}
#[test]
fn test_oauth_config_validation() {
let valid_config = OAuthConfig::new(
OAuthProvider::Google,
"client_id".to_string(),
"client_secret".to_string(),
"http://localhost/callback".to_string(),
);
assert!(valid_config.is_valid());
let mut invalid_config = valid_config.clone();
invalid_config.client_id = String::new();
assert!(!invalid_config.is_valid());
}
}

View file

@ -354,64 +354,3 @@ pub fn get_enabled_providers(
.filter(|config| config.is_valid())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_auth_url() {
let config = OAuthConfig::new(
OAuthProvider::Google,
"test_client_id".to_string(),
"test_secret".to_string(),
"http://localhost:8300/callback".to_string(),
);
let url = OAuthProvider::Google.build_auth_url(&config, "test_state");
assert!(url.starts_with("https://accounts.google.com/o/oauth2/v2/auth?"));
assert!(url.contains("client_id=test_client_id"));
assert!(url.contains("state=test_state"));
assert!(url.contains("response_type=code"));
}
#[test]
fn test_load_oauth_config() {
let mut bot_config = HashMap::new();
bot_config.insert("oauth-google-enabled".to_string(), "true".to_string());
bot_config.insert(
"oauth-google-client-id".to_string(),
"my_client_id".to_string(),
);
bot_config.insert(
"oauth-google-client-secret".to_string(),
"my_secret".to_string(),
);
let config = load_oauth_config(OAuthProvider::Google, &bot_config, "http://localhost:8300");
assert!(config.is_some());
let config = config.unwrap();
assert_eq!(config.client_id, "my_client_id");
assert!(config.redirect_uri.contains("/auth/oauth/google/callback"));
}
#[test]
fn test_disabled_provider() {
let mut bot_config = HashMap::new();
bot_config.insert("oauth-google-enabled".to_string(), "false".to_string());
bot_config.insert(
"oauth-google-client-id".to_string(),
"my_client_id".to_string(),
);
bot_config.insert(
"oauth-google-client-secret".to_string(),
"my_secret".to_string(),
);
let config = load_oauth_config(OAuthProvider::Google, &bot_config, "http://localhost:8300");
assert!(config.is_none());
}
}

View file

@ -425,184 +425,3 @@ fn sha256_hex(data: &[u8]) -> String {
let result = hasher.finalize();
hex::encode(result)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
fn create_test_config(dir: &Path) -> Result<()> {
let config = r#"
[cache_settings]
cache_dir = "test-cache"
[components.test]
name = "Test Component"
url = "https://example.com/test.tar.gz"
filename = "test.tar.gz"
sha256 = ""
[models.test_model]
name = "Test Model"
url = "https://example.com/model.gguf"
filename = "model.gguf"
sha256 = ""
"#;
let config_path = dir.join(CONFIG_FILE);
fs::write(config_path, config)?;
Ok(())
}
#[test]
fn test_extract_filename() {
assert_eq!(
DownloadCache::extract_filename("https://example.com/path/file.tar.gz"),
"file.tar.gz"
);
assert_eq!(
DownloadCache::extract_filename("https://example.com/file.zip?token=abc"),
"file.zip"
);
assert_eq!(DownloadCache::extract_filename("https://example.com/"), "");
}
#[test]
fn test_cache_creation() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
assert!(cache.cache_dir().exists());
assert_eq!(cache.cache_dir().file_name().unwrap(), "test-cache");
Ok(())
}
#[test]
fn test_is_cached() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
// Initially not cached
assert!(!cache.is_cached("test.tar.gz"));
// Create a cached file
let cache_path = cache.get_cache_path("test.tar.gz");
let mut file = fs::File::create(&cache_path)?;
file.write_all(b"test content")?;
// Now it should be cached
assert!(cache.is_cached("test.tar.gz"));
// Empty file should not count as cached
let empty_path = cache.get_cache_path("empty.tar.gz");
fs::File::create(&empty_path)?;
assert!(!cache.is_cached("empty.tar.gz"));
Ok(())
}
#[test]
fn test_resolve_url() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
// Test with uncached URL
let result = cache.resolve_url("https://example.com/newfile.tar.gz");
assert!(!result.is_cached());
assert_eq!(result.url(), Some("https://example.com/newfile.tar.gz"));
// Create cached file
let cache_path = cache.get_cache_path("newfile.tar.gz");
let mut file = fs::File::create(&cache_path)?;
file.write_all(b"cached content")?;
// Now it should resolve to cached
let result = cache.resolve_url("https://example.com/newfile.tar.gz");
assert!(result.is_cached());
assert!(result.url().is_none());
Ok(())
}
#[test]
fn test_get_component() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
let component = cache.get_component("test");
assert!(component.is_some());
assert_eq!(component.unwrap().name, "Test Component");
let missing = cache.get_component("nonexistent");
assert!(missing.is_none());
Ok(())
}
#[test]
fn test_list_cached() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
// Create some cached files
fs::write(cache.get_cache_path("file1.tar.gz"), "content1")?;
fs::write(cache.get_cache_path("file2.zip"), "content2")?;
let files = cache.list_cached()?;
assert_eq!(files.len(), 2);
assert!(files.contains(&"file1.tar.gz".to_string()));
assert!(files.contains(&"file2.zip".to_string()));
Ok(())
}
#[test]
fn test_cache_size() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
// Initially empty
assert_eq!(cache.cache_size()?, 0);
// Add files
fs::write(cache.get_cache_path("file1.txt"), "12345")?; // 5 bytes
fs::write(cache.get_cache_path("file2.txt"), "1234567890")?; // 10 bytes
assert_eq!(cache.cache_size()?, 15);
Ok(())
}
#[test]
fn test_clear_cache() -> Result<()> {
let temp_dir = TempDir::new()?;
create_test_config(temp_dir.path())?;
let cache = DownloadCache::new(temp_dir.path())?;
// Create some cached files
fs::write(cache.get_cache_path("file1.tar.gz"), "content1")?;
fs::write(cache.get_cache_path("file2.zip"), "content2")?;
assert_eq!(cache.list_cached()?.len(), 2);
cache.clear_cache()?;
assert_eq!(cache.list_cached()?.len(), 0);
Ok(())
}
}

View file

@ -496,17 +496,3 @@ TLS:
fs::write(config_path, yaml_config).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_directory_setup_creation() {
let setup = DirectorySetup::new(
"http://localhost:8080".to_string(),
PathBuf::from("/tmp/directory_config.json"),
);
assert_eq!(setup.base_url, "http://localhost:8080");
}
}

View file

@ -383,32 +383,3 @@ directory = "local"
fs::write(config_path, config).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_email_setup_creation() {
let setup = EmailSetup::new(
"http://localhost:8080".to_string(),
PathBuf::from("/tmp/email_config.json"),
);
assert_eq!(setup.base_url, "http://localhost:8080");
}
#[tokio::test]
async fn test_generate_config() {
let config_path = std::env::temp_dir().join("email_test_config.toml");
let data_path = std::env::temp_dir().join("email_data");
generate_email_config(config_path.clone(), data_path, false)
.await
.unwrap();
assert!(config_path.exists());
// Cleanup
let _ = std::fs::remove_file(config_path);
}
}

Some files were not shown because too many files have changed in this diff Show more