Update server components and keywords
This commit is contained in:
parent
a491cc13a6
commit
e2a5bf091a
126 changed files with 398 additions and 7455 deletions
54
Cargo.toml
54
Cargo.toml
|
|
@ -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
|
||||
|
|
|
|||
33
PROMPT.md
33
PROMPT.md
|
|
@ -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 _
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", ¶ms);
|
||||
assert!(envelope.contains("<GetUser"));
|
||||
assert!(envelope.contains("<name>John</name>"));
|
||||
assert!(envelope.contains("<age>30</age>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_soap_response() {
|
||||
let xml = r#"<?xml version="1.0"?><soap:Envelope><soap:Body><Result>Success</Result></soap:Body></soap:Envelope>"#;
|
||||
let result = parse_soap_response(xml);
|
||||
assert!(result.get("raw").is_some());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("||"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue