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"
|
askama_axum = "0.4"
|
||||||
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
uuid = { version = "1.11", features = ["serde", "v4"] }
|
uuid = { version = "1.11", features = ["serde", "v4", "v5"] }
|
||||||
|
|
||||||
# === TLS/SECURITY DEPENDENCIES ===
|
# === TLS/SECURITY DEPENDENCIES ===
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
||||||
|
|
@ -242,53 +242,5 @@ mockito = "1.7.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
# === SECURITY AND CODE QUALITY CONFIGURATION ===
|
# === SECURITY AND CODE QUALITY CONFIGURATION ===
|
||||||
[lints.rust]
|
[lints]
|
||||||
unused_imports = "warn"
|
workspace = true
|
||||||
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
|
|
||||||
|
|
|
||||||
33
PROMPT.md
33
PROMPT.md
|
|
@ -38,8 +38,13 @@ todo = "warn"
|
||||||
❌ NEVER leave dead code - DELETE it or IMPLEMENT it
|
❌ NEVER leave dead code - DELETE it or IMPLEMENT it
|
||||||
❌ NEVER use approximate constants (3.14159) - use std::f64::consts::PI
|
❌ 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 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 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 { }
|
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
|
### Const Functions
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
@ -368,6 +398,7 @@ src/shared/models.rs # Database models
|
||||||
## Remember
|
## Remember
|
||||||
|
|
||||||
- **ZERO WARNINGS** - Every clippy warning must be fixed
|
- **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
|
- **NO ALLOW IN CODE** - Never use #[allow()] in source files
|
||||||
- **CARGO.TOML EXCEPTIONS OK** - Disable lints with false positives in Cargo.toml with comment
|
- **CARGO.TOML EXCEPTIONS OK** - Disable lints with false positives in Cargo.toml with comment
|
||||||
- **NO DEAD CODE** - Delete unused code, never prefix with _
|
- **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(
|
pub async fn generate_tips(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(request): Json<TipRequest>,
|
Json(request): Json<TipRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> (StatusCode, Json<TipResponse>) {
|
||||||
info!("Generating tips for session {}", request.session_id);
|
info!("Generating tips for session {}", request.session_id);
|
||||||
|
|
||||||
// Get session and bot info
|
// Get session and bot info
|
||||||
|
|
@ -528,7 +528,7 @@ Provide tips for the attendant."#,
|
||||||
pub async fn polish_message(
|
pub async fn polish_message(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(request): Json<PolishRequest>,
|
Json(request): Json<PolishRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> (StatusCode, Json<PolishResponse>) {
|
||||||
info!("Polishing message for session {}", request.session_id);
|
info!("Polishing message for session {}", request.session_id);
|
||||||
|
|
||||||
let session_result = get_session(&state, request.session_id).await;
|
let session_result = get_session(&state, request.session_id).await;
|
||||||
|
|
@ -626,7 +626,7 @@ Respond in JSON format:
|
||||||
pub async fn generate_smart_replies(
|
pub async fn generate_smart_replies(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(request): Json<SmartRepliesRequest>,
|
Json(request): Json<SmartRepliesRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> (StatusCode, Json<SmartRepliesResponse>) {
|
||||||
info!(
|
info!(
|
||||||
"Generating smart replies for session {}",
|
"Generating smart replies for session {}",
|
||||||
request.session_id
|
request.session_id
|
||||||
|
|
@ -730,7 +730,7 @@ Generate 3 reply options for the attendant."#,
|
||||||
pub async fn generate_summary(
|
pub async fn generate_summary(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Path(session_id): Path<Uuid>,
|
Path(session_id): Path<Uuid>,
|
||||||
) -> impl IntoResponse {
|
) -> (StatusCode, Json<SummaryResponse>) {
|
||||||
info!("Generating summary for session {}", session_id);
|
info!("Generating summary for session {}", session_id);
|
||||||
|
|
||||||
let session_result = get_session(&state, session_id).await;
|
let session_result = get_session(&state, session_id).await;
|
||||||
|
|
@ -1133,13 +1133,13 @@ async fn handle_take_command(
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("Unknown");
|
.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.",
|
" *Conversation assigned*\n\nCustomer: *{}*\nSession: {}\n\nYou can now respond to this customer. Their messages will be forwarded to you.",
|
||||||
name,
|
name,
|
||||||
&session.id.to_string()[..8]
|
&session.id.to_string()[..8]
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(" No conversations waiting in queue.".to_string())
|
Ok::<String, String>(" No conversations waiting in queue.".to_string())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
@ -1196,6 +1196,7 @@ async fn handle_status_command(
|
||||||
.load(&mut db_conn)
|
.load(&mut db_conn)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let session_count = sessions.len();
|
||||||
for session in sessions {
|
for session in sessions {
|
||||||
let mut ctx = session.context_data.clone();
|
let mut ctx = session.context_data.clone();
|
||||||
ctx["attendant_status"] = serde_json::json!(status_val);
|
ctx["attendant_status"] = serde_json::json!(status_val);
|
||||||
|
|
@ -1207,7 +1208,7 @@ async fn handle_status_command(
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<usize, String>(sessions.len())
|
Ok::<usize, String>(session_count)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
@ -1369,8 +1370,7 @@ async fn handle_tips_command(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate tips
|
// Generate tips
|
||||||
let response = generate_tips(State(state.clone()), Json(request)).await;
|
let (_, Json(tip_response)) = generate_tips(State(state.clone()), Json(request)).await;
|
||||||
let (_, Json(tip_response)) = response.into_response().into_parts();
|
|
||||||
|
|
||||||
if tip_response.tips.is_empty() {
|
if tip_response.tips.is_empty() {
|
||||||
return Ok(" No specific tips for this conversation yet.".to_string());
|
return Ok(" No specific tips for this conversation yet.".to_string());
|
||||||
|
|
@ -1410,8 +1410,7 @@ async fn handle_polish_command(
|
||||||
tone: "professional".to_string(),
|
tone: "professional".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = polish_message(State(state.clone()), Json(request)).await;
|
let (_, Json(polish_response)) = polish_message(State(state.clone()), Json(request)).await;
|
||||||
let (_, Json(polish_response)) = response.into_response().into_parts();
|
|
||||||
|
|
||||||
if !polish_response.success {
|
if !polish_response.success {
|
||||||
return Err(polish_response
|
return Err(polish_response
|
||||||
|
|
@ -1447,8 +1446,7 @@ async fn handle_replies_command(
|
||||||
history,
|
history,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = generate_smart_replies(State(state.clone()), Json(request)).await;
|
let (_, Json(replies_response)) = generate_smart_replies(State(state.clone()), Json(request)).await;
|
||||||
let (_, Json(replies_response)) = response.into_response().into_parts();
|
|
||||||
|
|
||||||
if replies_response.replies.is_empty() {
|
if replies_response.replies.is_empty() {
|
||||||
return Ok(" No reply suggestions available.".to_string());
|
return Ok(" No reply suggestions available.".to_string());
|
||||||
|
|
@ -1476,8 +1474,7 @@ async fn handle_summary_command(
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let session_id = current_session.ok_or("No active conversation")?;
|
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)) = generate_summary(State(state.clone()), Path(session_id)).await;
|
||||||
let (_, Json(summary_response)) = response.into_response().into_parts();
|
|
||||||
|
|
||||||
if !summary_response.success {
|
if !summary_response.success {
|
||||||
return Err(summary_response
|
return Err(summary_response
|
||||||
|
|
@ -2102,76 +2099,3 @@ fn analyze_sentiment_keywords(message: &str) -> SentimentAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
// 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
|
// Not a GOTO line, return as-is
|
||||||
trimmed.to_string()
|
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
|
// 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>)]
|
#[diesel(sql_type = diesel::sql_types::Nullable<diesel::sql_types::Text>)]
|
||||||
model_config: Option<String>,
|
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);
|
trace!("Created communication channel for team {}", team_name);
|
||||||
Ok(())
|
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(())
|
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
|
// 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
|
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");
|
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");
|
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()
|
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
|
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
|
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(())
|
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)
|
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
|
// 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");
|
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(())
|
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();
|
.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 conn = state.conn.clone();
|
||||||
|
|
||||||
let result = std::thread::spawn(move || {
|
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 conn = state.conn.clone();
|
||||||
|
|
||||||
let result = std::thread::spawn(move || {
|
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>,
|
state: &Arc<AppState>,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
attendant_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>,
|
state: &Arc<AppState>,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
reason: Option<String>,
|
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 conn = state.conn.clone();
|
||||||
let session_uuid = match Uuid::parse_str(session_id) {
|
let session_uuid = match Uuid::parse_str(session_id) {
|
||||||
Ok(u) => u,
|
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
|
// Read from attendant.csv
|
||||||
let work_path = std::env::var("WORK_PATH").unwrap_or_else(|_| "./work".to_string());
|
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();
|
.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 conn = state.conn.clone();
|
||||||
let att_id = attendant_id.to_string();
|
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)
|
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 msg_lower = message.to_lowercase();
|
||||||
let mut tips = Vec::new();
|
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)
|
// Simple polishing without LLM (fallback)
|
||||||
let mut polished = message.to_string();
|
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)
|
// Return default replies (fallback without LLM)
|
||||||
let mut replies = Vec::new();
|
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 conn = state.conn.clone();
|
||||||
let session_uuid = match Uuid::parse_str(session_id) {
|
let session_uuid = match Uuid::parse_str(session_id) {
|
||||||
Ok(u) => u,
|
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)
|
// Keyword-based sentiment analysis (fallback without LLM)
|
||||||
let msg_lower = message.to_lowercase();
|
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 conn = state.conn.clone();
|
||||||
let session_uuid = match Uuid::parse_str(session_id) {
|
let session_uuid = match Uuid::parse_str(session_id) {
|
||||||
Ok(u) => u,
|
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>,
|
state: &Arc<AppState>,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
note: &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 conn = state.conn.clone();
|
||||||
let user_uuid = match Uuid::parse_str(user_id) {
|
let user_uuid = match Uuid::parse_str(user_id) {
|
||||||
Ok(u) => u,
|
Ok(u) => u,
|
||||||
|
|
@ -1614,7 +1614,7 @@ fn get_customer_history_impl(state: &Arc<AppState>, user_id: &str) -> Dynamic {
|
||||||
|
|
||||||
// Helper Functions
|
// Helper Functions
|
||||||
|
|
||||||
fn create_error_result(message: &str) -> Dynamic {
|
pub fn create_error_result(message: &str) -> Dynamic {
|
||||||
let mut result = Map::new();
|
let mut result = Map::new();
|
||||||
result.insert("success".into(), Dynamic::from(false));
|
result.insert("success".into(), Dynamic::from(false));
|
||||||
result.insert("error".into(), Dynamic::from(message.to_string()));
|
result.insert("error".into(), Dynamic::from(message.to_string()));
|
||||||
|
|
@ -1622,137 +1622,3 @@ fn create_error_result(message: &str) -> Dynamic {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
// 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 {
|
fn sanitize_sql(value: &str) -> String {
|
||||||
value.replace('\'', "''")
|
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)))
|
.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();
|
let unit_lower = unit.to_lowercase();
|
||||||
|
|
||||||
if let Some(datetime) = parse_datetime(date_str) {
|
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();
|
let unit_lower = unit.to_lowercase();
|
||||||
|
|
||||||
if let (Some(dt1), Some(dt2)) = (parse_datetime(date1), parse_datetime(date2)) {
|
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
|
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");
|
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) {
|
if let Some(datetime) = parse_datetime(date_str) {
|
||||||
let chrono_format = format
|
let chrono_format = format
|
||||||
.replace("YYYY", "%Y")
|
.replace("YYYY", "%Y")
|
||||||
|
|
@ -166,34 +166,3 @@ fn format_date_impl(date_str: &str, format: &str) -> String {
|
||||||
date_str.to_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");
|
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");
|
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
|
// - Stack trace capture
|
||||||
// - Error context/metadata
|
// - Error context/metadata
|
||||||
// - Retry mechanisms
|
// - 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())
|
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
|
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())
|
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?;
|
let stats = get_kb_statistics(state, user).await?;
|
||||||
Ok(stats.total_disk_size_mb)
|
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 SKILL: &str = "skill";
|
||||||
pub const TECHNOLOGY: &str = "technology";
|
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");
|
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())
|
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");
|
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");
|
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");
|
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");
|
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");
|
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");
|
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");
|
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 McpDirectoryScanner = McpCsvLoader;
|
||||||
pub type McpDirectoryScanResult = McpLoadResult;
|
pub type McpDirectoryScanResult = McpLoadResult;
|
||||||
pub type McpScanError = McpLoadError;
|
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)
|
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
|
// 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(())
|
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>()
|
.collect::<String>()
|
||||||
.to_lowercase()
|
.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(())
|
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
|
// 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())
|
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)]
|
#[diesel(sql_type = diesel::sql_types::Jsonb)]
|
||||||
value: serde_json::Value,
|
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)
|
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
|
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");
|
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
|
"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())
|
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()
|
..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)
|
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");
|
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
|
/// # Returns
|
||||||
/// * 1-based position of the first occurrence, or 0 if not found
|
/// * 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
|
// Handle edge cases
|
||||||
if haystack.is_empty() || needle.is_empty() {
|
if haystack.is_empty() || needle.is_empty() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -120,7 +120,7 @@ pub fn is_numeric_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &m
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `true` if the value can be parsed as a number
|
/// * `true` if the value can be parsed as a number
|
||||||
/// * `false` otherwise
|
/// * `false` otherwise
|
||||||
fn is_numeric_impl(value: &str) -> bool {
|
pub fn is_numeric_impl(value: &str) -> bool {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
|
|
||||||
// Empty string is not numeric
|
// 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);
|
mid_keyword(&state, user.clone(), engine);
|
||||||
replace_keyword(&state, user, 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
|
/// Compares two Dynamic values for equality, handling type coercion
|
||||||
/// where appropriate.
|
/// 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
|
// Try string comparison first
|
||||||
if let (Some(e), Some(c)) = (
|
if let (Some(e), Some(c)) = (
|
||||||
expr.clone().into_string().ok(),
|
expr.clone().into_string().ok(),
|
||||||
|
|
@ -219,78 +219,3 @@ pub fn preprocess_switch(input: &str) -> String {
|
||||||
|
|
||||||
result
|
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
|
// This function exists for consistency with other keyword modules
|
||||||
trace!("TABLE keyword registered (compile-time only)");
|
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 access_token: String,
|
||||||
pub refresh_token: Option<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))
|
.map(|r| (r.kb_name, r.kb_folder_path, r.qdrant_collection))
|
||||||
.collect())
|
.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>()
|
.collect::<String>()
|
||||||
.to_lowercase()
|
.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
|
// 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
|
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");
|
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");
|
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");
|
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
|
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
|
// For now, return error indicating configuration needed
|
||||||
Err("Weather API key not configured. Please set 'weather-api-key' in config.csv".to_string())
|
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())
|
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/event/new", get(new_event_form))
|
||||||
.route("/ui/calendar/new", get(new_calendar_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)>,
|
modified: Vec<(Uuid, AccessLevel)>,
|
||||||
comments: String,
|
comments: String,
|
||||||
) -> Result<AccessReviewResult> {
|
) -> Result<AccessReviewResult> {
|
||||||
let review = self
|
// Get review info first
|
||||||
.reviews
|
let (reviewer_id, user_id) = {
|
||||||
.get_mut(&review_id)
|
let review = self
|
||||||
.ok_or_else(|| anyhow!("Review not found"))?;
|
.reviews
|
||||||
|
.get(&review_id)
|
||||||
|
.ok_or_else(|| anyhow!("Review not found"))?;
|
||||||
|
|
||||||
if review.status != ReviewStatus::Pending && review.status != ReviewStatus::InProgress {
|
if review.status != ReviewStatus::Pending && review.status != ReviewStatus::InProgress {
|
||||||
return Err(anyhow!("Review already completed"));
|
return Err(anyhow!("Review already completed"));
|
||||||
}
|
}
|
||||||
|
(review.reviewer_id, review.user_id)
|
||||||
|
};
|
||||||
|
|
||||||
// Process revocations
|
// Process revocations
|
||||||
for perm_id in &revoked {
|
for perm_id in &revoked {
|
||||||
self.revoke_permission(*perm_id, review.reviewer_id)?;
|
self.revoke_permission(*perm_id, reviewer_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process modifications
|
// Process modifications
|
||||||
for (perm_id, new_level) in &modified {
|
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) {
|
if let Some(perm) = permissions.iter_mut().find(|p| p.id == *perm_id) {
|
||||||
perm.access_level = new_level.clone();
|
perm.access_level = new_level.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
review.status = ReviewStatus::Approved;
|
// Update review status
|
||||||
review.comments = Some(comments.clone());
|
if let Some(review) = self.reviews.get_mut(&review_id) {
|
||||||
|
review.status = ReviewStatus::Approved;
|
||||||
|
review.comments = Some(comments.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let result = AccessReviewResult {
|
let result = AccessReviewResult {
|
||||||
review_id,
|
review_id,
|
||||||
reviewer_id: review.reviewer_id,
|
reviewer_id,
|
||||||
reviewed_at: Utc::now(),
|
reviewed_at: Utc::now(),
|
||||||
approved_permissions: approved,
|
approved_permissions: approved,
|
||||||
revoked_permissions: revoked,
|
revoked_permissions: revoked,
|
||||||
|
|
|
||||||
|
|
@ -658,83 +658,3 @@ fn escape_csv(s: &str) -> String {
|
||||||
s.to_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 low_issues: usize,
|
||||||
pub results: Vec<ComplianceCheckResult>,
|
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
|
/// Check all active policies
|
||||||
pub fn check_all_policies(&mut self, context: &PolicyContext) -> Vec<PolicyCheckResult> {
|
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();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
for policy in self.policies.values() {
|
for policy_id in active_policy_ids {
|
||||||
if policy.status != PolicyStatus::Active {
|
let result = self.check_policy(policy_id, context);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = self.check_policy(policy.id, context);
|
|
||||||
if let Ok(result) = result {
|
if let Ok(result) = result {
|
||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -258,43 +258,56 @@ impl TrainingTracker {
|
||||||
attempt_id: Uuid,
|
attempt_id: Uuid,
|
||||||
score: u32,
|
score: u32,
|
||||||
) -> Result<bool> {
|
) -> 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
|
let assignment = self
|
||||||
.assignments
|
.assignments
|
||||||
.get_mut(&assignment_id)
|
.get_mut(&assignment_id)
|
||||||
.ok_or_else(|| anyhow!("Assignment not found"))?;
|
.ok_or_else(|| anyhow!("Assignment not found"))?;
|
||||||
|
|
||||||
let course = self
|
let attempt_idx = assignment
|
||||||
.courses
|
|
||||||
.get(&assignment.course_id)
|
|
||||||
.ok_or_else(|| anyhow!("Course not found"))?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let attempt = assignment
|
|
||||||
.attempts
|
.attempts
|
||||||
.iter_mut()
|
.iter()
|
||||||
.find(|a| a.id == attempt_id)
|
.position(|a| a.id == attempt_id)
|
||||||
.ok_or_else(|| anyhow!("Attempt not found"))?;
|
.ok_or_else(|| anyhow!("Attempt not found"))?;
|
||||||
|
|
||||||
let end_time = Utc::now();
|
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);
|
// Update attempt
|
||||||
attempt.score = Some(score);
|
assignment.attempts[attempt_idx].end_time = Some(end_time);
|
||||||
attempt.time_spent_minutes = Some(time_spent);
|
assignment.attempts[attempt_idx].score = Some(score);
|
||||||
attempt.passed = score >= course.passing_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.status = TrainingStatus::Completed;
|
||||||
assignment.completion_date = Some(end_time);
|
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
|
// Issue certificate
|
||||||
let certificate = TrainingCertificate {
|
let certificate = TrainingCertificate {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
user_id: assignment.user_id,
|
user_id,
|
||||||
course_id: course.id,
|
course_id,
|
||||||
issued_date: end_time,
|
issued_date: end_time,
|
||||||
expiry_date: end_time + Duration::days(course.validity_days),
|
expiry_date: end_time + Duration::days(validity_days),
|
||||||
certificate_number: format!(
|
certificate_number: format!(
|
||||||
"CERT-{}",
|
"CERT-{}",
|
||||||
Uuid::new_v4().to_string()[..8].to_uppercase()
|
Uuid::new_v4().to_string()[..8].to_uppercase()
|
||||||
|
|
@ -306,15 +319,15 @@ impl TrainingTracker {
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"User {} completed training '{}' with score {}",
|
"User {} completed training '{}' with score {}",
|
||||||
assignment.user_id,
|
user_id,
|
||||||
course.title,
|
course_title,
|
||||||
score
|
score
|
||||||
);
|
);
|
||||||
} else if assignment.attempts.len() >= course.max_attempts as usize {
|
} else if attempts_count >= max_attempts as usize {
|
||||||
assignment.status = TrainingStatus::Failed;
|
assignment.status = TrainingStatus::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attempt.passed)
|
Ok(passed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get user compliance status
|
/// Get user compliance status
|
||||||
|
|
|
||||||
|
|
@ -939,31 +939,3 @@ pub fn apply_wizard_config(config: &WizardConfig) -> io::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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/unknown task type
|
||||||
Default,
|
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)
|
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
|
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
|
&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(())
|
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,
|
Modified,
|
||||||
Deleted,
|
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()
|
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())
|
.filter(|config| config.is_valid())
|
||||||
.collect()
|
.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();
|
let result = hasher.finalize();
|
||||||
hex::encode(result)
|
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?;
|
fs::write(config_path, yaml_config).await?;
|
||||||
Ok(())
|
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?;
|
fs::write(config_path, config).await?;
|
||||||
Ok(())
|
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