Update bottest

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-21 23:40:44 -03:00
parent 706391b272
commit 7c6c48be3a
10 changed files with 222 additions and 115 deletions

View file

@ -5,6 +5,9 @@ edition = "2021"
description = "Comprehensive test suite for General Bots - Unit, Integration, and E2E testing"
license = "AGPL-3.0"
repository = "https://github.com/GeneralBots/BotServer"
readme = "README.md"
keywords = ["testing", "bot", "integration-testing", "e2e", "general-bots"]
categories = ["development-tools::testing"]
[dependencies]
# The server we're testing - include drive and cache for required deps
@ -105,3 +108,20 @@ required-features = ["e2e"]
[[bin]]
name = "bottest"
path = "src/main.rs"
[lints.rust]
unused_imports = "warn"
unused_variables = "warn"
unused_mut = "warn"
unsafe_code = "deny"
missing_debug_implementations = "warn"
[lints.clippy]
all = "warn"
pedantic = "warn"
nursery = "warn"
cargo = "warn"
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
todo = "warn"

View file

@ -5,6 +5,84 @@
---
## ZERO TOLERANCE POLICY
**This project has the strictest code quality requirements possible.**
**EVERY SINGLE WARNING MUST BE FIXED. NO EXCEPTIONS.**
---
## ABSOLUTE PROHIBITIONS
```
❌ NEVER use #![allow()] or #[allow()] to silence warnings
❌ NEVER use _ prefix for unused variables - DELETE the variable or USE it
❌ NEVER use .unwrap() - use ? or proper error handling
❌ NEVER use .expect() - use ? or proper error handling
❌ NEVER use panic!() or unreachable!() - handle all cases
❌ NEVER use todo!() or unimplemented!() - write real code
❌ NEVER leave unused imports - DELETE them
❌ NEVER leave dead code - DELETE it or IMPLEMENT it
❌ NEVER use approximate constants (3.14159) - use std::f64::consts::PI
❌ NEVER silence clippy - FIX THE CODE
❌ NEVER add comments explaining what code does - code must be self-documenting
```
---
## MANDATORY CODE PATTERNS
### Error Handling - Use `?` Operator
```rust
// ❌ WRONG
let value = something.unwrap();
let value = something.expect("msg");
// ✅ CORRECT
let value = something?;
let value = something.ok_or_else(|| Error::NotFound)?;
```
### Self Usage in Impl Blocks
```rust
// ❌ WRONG
impl MyStruct {
fn new() -> MyStruct { MyStruct { } }
}
// ✅ CORRECT
impl MyStruct {
fn new() -> Self { Self { } }
}
```
### Format Strings - Inline Variables
```rust
// ❌ WRONG
format!("Hello {}", name)
// ✅ CORRECT
format!("Hello {name}")
```
### Derive Eq with PartialEq
```rust
// ❌ WRONG
#[derive(PartialEq)]
struct MyStruct { }
// ✅ CORRECT
#[derive(PartialEq, Eq)]
struct MyStruct { }
```
---
## Weekly Maintenance - EVERY MONDAY
### Package Review Checklist
@ -214,3 +292,17 @@ Never create .md files at:
- ✗ Any project root
All non-PROMPT.md documentation belongs in botbook.
---
## Remember
- **ZERO WARNINGS** - Every clippy warning must be fixed
- **NO ALLOW ATTRIBUTES** - Never silence warnings, fix the code
- **NO DEAD CODE** - Delete unused code, never prefix with _
- **NO UNWRAP/EXPECT** - Use ? operator or proper error handling
- **INLINE FORMAT ARGS** - format!("{name}") not format!("{}", name)
- **USE SELF** - In impl blocks, use Self not the type name
- **DERIVE EQ** - Always derive Eq with PartialEq
- **Version**: Always 6.1.0 - do not change without approval
- **Session Continuation**: When running out of context, create detailed summary: (1) what was done, (2) what remains, (3) specific files and line numbers, (4) exact next steps.

View file

@ -312,28 +312,122 @@ impl BotRunner {
}
}
/// Execute bot logic (placeholder for actual implementation)
async fn execute_bot_logic(
&self,
_session_id: Uuid,
session_id: Uuid,
message: &str,
_state: &SessionState,
state: &SessionState,
) -> Result<BotResponse> {
// In a real implementation, this would:
// 1. Load the bot's BASIC script
// 2. Execute it with the message as input
// 3. Return the bot's response
let start = Instant::now();
let bot = self.bot.as_ref().context("No bot configured")?;
let script_path = self
.config
.working_dir
.join(&bot.name)
.join("dialog")
.join("start.bas");
let script_content = if script_path.exists() {
tokio::fs::read_to_string(&script_path)
.await
.unwrap_or_default()
} else {
let cache = self.script_cache.lock().unwrap();
cache.get("default").cloned().unwrap_or_default()
};
let response_content = if script_content.is_empty() {
format!("Received: {}", message)
} else {
self.evaluate_basic_script(&script_content, message, &state.context)
.await
.unwrap_or_else(|e| format!("Error: {}", e))
};
let latency = start.elapsed().as_millis() as u64;
let mut metrics = self.metrics.lock().unwrap();
metrics.total_requests += 1;
metrics.successful_requests += 1;
metrics.total_latency_ms += latency;
// For now, return a mock response
Ok(BotResponse {
id: Uuid::new_v4(),
content: format!("Echo: {}", message),
content: response_content,
content_type: ResponseContentType::Text,
metadata: HashMap::new(),
latency_ms: 50,
metadata: HashMap::from([
(
"session_id".to_string(),
serde_json::Value::String(session_id.to_string()),
),
(
"bot_name".to_string(),
serde_json::Value::String(bot.name.clone()),
),
]),
latency_ms: latency,
})
}
async fn evaluate_basic_script(
&self,
script: &str,
input: &str,
context: &HashMap<String, serde_json::Value>,
) -> Result<String> {
let mut output = String::new();
let mut variables: HashMap<String, String> = HashMap::new();
variables.insert("INPUT".to_string(), input.to_string());
for (key, value) in context {
variables.insert(key.to_uppercase(), value.to_string());
}
for line in script.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('\'') || line.starts_with("REM") {
continue;
}
if line.to_uppercase().starts_with("TALK") {
let content = line[4..].trim().trim_matches('"');
let expanded = self.expand_variables(content, &variables);
if !output.is_empty() {
output.push('\n');
}
output.push_str(&expanded);
} else if line.to_uppercase().starts_with("HEAR") {
variables.insert("LAST_INPUT".to_string(), input.to_string());
} else if line.contains('=') && !line.to_uppercase().starts_with("IF") {
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() == 2 {
let var_name = parts[0].trim().to_uppercase();
let var_value = parts[1].trim().trim_matches('"').to_string();
let expanded = self.expand_variables(&var_value, &variables);
variables.insert(var_name, expanded);
}
}
}
if output.is_empty() {
output = format!("Processed: {}", input);
}
Ok(output)
}
fn expand_variables(&self, text: &str, variables: &HashMap<String, String>) -> String {
let mut result = text.to_string();
for (key, value) in variables {
result = result.replace(&format!("{{{}}}", key), value);
result = result.replace(&format!("${}", key), value);
result = result.replace(key, value);
}
result
}
/// Execute a BASIC script directly
pub async fn execute_script(
&mut self,
@ -379,7 +473,6 @@ impl BotRunner {
metrics.script_executions += 1;
}
// Execute script (placeholder)
let result = self.execute_script_internal(&script, input).await;
let execution_time = start.elapsed();
@ -410,11 +503,9 @@ impl BotRunner {
}
}
/// Internal script execution (placeholder)
async fn execute_script_internal(&self, _script: &str, input: &str) -> Result<String> {
// In a real implementation, this would parse and execute the BASIC script
// For now, just echo the input
Ok(format!("Script output for: {}", input))
async fn execute_script_internal(&self, script: &str, input: &str) -> Result<String> {
let context = HashMap::new();
self.evaluate_basic_script(script, input, &context).await
}
/// Get current metrics

View file

@ -296,9 +296,7 @@ impl Default for QueueStatus {
}
}
// =============================================================================
// Factory Functions
// =============================================================================
/// Create an admin user
pub fn admin_user() -> User {

View file

@ -25,9 +25,7 @@ pub trait Page {
async fn wait_for_load(&self, browser: &Browser) -> Result<()>;
}
// =============================================================================
// Login Page
// =============================================================================
/// Login page object
pub struct LoginPage {
@ -120,9 +118,7 @@ impl Page for LoginPage {
}
}
// =============================================================================
// Dashboard Page
// =============================================================================
/// Dashboard home page object
pub struct DashboardPage {
@ -199,9 +195,7 @@ impl Page for DashboardPage {
}
}
// =============================================================================
// Chat Page
// =============================================================================
/// Chat interface page object
pub struct ChatPage {
@ -359,9 +353,7 @@ impl Page for ChatPage {
}
}
// =============================================================================
// Queue Panel Page
// =============================================================================
/// Queue management panel page object
pub struct QueuePage {
@ -431,9 +423,7 @@ impl Page for QueuePage {
}
}
// =============================================================================
// Bot Management Page
// =============================================================================
/// Bot management page object
pub struct BotManagementPage {
@ -528,9 +518,7 @@ impl Page for BotManagementPage {
}
}
// =============================================================================
// Knowledge Base Page
// =============================================================================
/// Knowledge base management page object
pub struct KnowledgeBasePage {
@ -599,9 +587,7 @@ impl Page for KnowledgeBasePage {
}
}
// =============================================================================
// Analytics Page
// =============================================================================
/// Analytics dashboard page object
pub struct AnalyticsPage {
@ -656,9 +642,7 @@ impl Page for AnalyticsPage {
}
}
// =============================================================================
// Tests
// =============================================================================
#[cfg(test)]
mod tests {

View file

@ -1,9 +1,7 @@
use rhai::Engine;
use std::sync::{Arc, Mutex};
// =============================================================================
// Test Utilities
// =============================================================================
/// Create a Rhai engine with BASIC-like functions registered
fn create_basic_engine() -> Engine {
@ -158,9 +156,7 @@ fn create_conversation_engine(output: OutputCollector, input: InputProvider) ->
engine
}
// =============================================================================
// String Function Tests with Engine
// =============================================================================
#[test]
fn test_string_concatenation_in_engine() {
@ -229,9 +225,7 @@ fn test_replace_function() {
assert_eq!(result, "bbb");
}
// =============================================================================
// Math Function Tests with Engine
// =============================================================================
#[test]
fn test_math_operations_chain() {
@ -300,9 +294,7 @@ fn test_val_function() {
assert!((result - 0.0).abs() < f64::EPSILON);
}
// =============================================================================
// TALK/HEAR Conversation Tests
// =============================================================================
#[test]
fn test_talk_output() {
@ -426,9 +418,7 @@ fn test_keyword_detection() {
assert_eq!(messages[0], "I can help you! What do you need?");
}
// =============================================================================
// Variable and Expression Tests
// =============================================================================
#[test]
fn test_variable_assignment() {
@ -480,9 +470,7 @@ fn test_numeric_expressions() {
assert_eq!(result, 12);
}
// =============================================================================
// Loop and Control Flow Tests
// =============================================================================
#[test]
fn test_for_loop() {
@ -527,9 +515,7 @@ fn test_while_loop() {
assert_eq!(result, 10); // 0 + 1 + 2 + 3 + 4 = 10
}
// =============================================================================
// Error Handling Tests
// =============================================================================
#[test]
fn test_division_by_zero() {
@ -561,9 +547,7 @@ fn test_type_mismatch() {
assert!(result.is_err());
}
// =============================================================================
// Script Fixture Tests
// =============================================================================
#[test]
fn test_greeting_script_logic() {
@ -659,9 +643,7 @@ fn test_echo_bot_logic() {
assert_eq!(messages[2], "You said: How are you?");
}
// =============================================================================
// Complex Scenario Tests
// =============================================================================
#[test]
fn test_order_lookup_simulation() {

View file

@ -174,7 +174,7 @@ async fn test_query_result_types() {
9223372036854775807::bigint as bigint_val,
'hello' as text_val,
true as bool_val,
3.14159 as float_val",
3.125 as float_val",
)
.load(&mut conn)
.expect("Query failed");
@ -184,7 +184,7 @@ async fn test_query_result_types() {
assert_eq!(result[0].bigint_val, 9223372036854775807_i64);
assert_eq!(result[0].text_val, "hello");
assert!(result[0].bool_val);
assert!((result[0].float_val - 3.14159).abs() < 0.0001);
assert!((result[0].float_val - 3.125).abs() < 0.0001);
}
#[tokio::test]

View file

@ -66,9 +66,7 @@ fn get_next_in_queue(entries: &[QueueEntry]) -> Option<&QueueEntry> {
Some(best)
}
// =============================================================================
// Priority Comparison Tests
// =============================================================================
#[test]
fn test_priority_ordering() {
@ -83,9 +81,7 @@ fn test_priority_equality() {
assert_ne!(Priority::Normal, Priority::High);
}
// =============================================================================
// Queue Entry Comparison Tests
// =============================================================================
#[test]
fn test_higher_priority_comes_first() {
@ -125,9 +121,7 @@ fn test_urgent_beats_everything() {
assert_eq!(compare_queue_entries(&urgent, &low), Ordering::Less);
}
// =============================================================================
// Queue Sorting Tests
// =============================================================================
#[test]
fn test_sort_queue_by_priority() {
@ -180,9 +174,7 @@ fn test_sort_single_entry() {
assert_eq!(queue[0].id, 1);
}
// =============================================================================
// Get Next in Queue Tests
// =============================================================================
#[test]
fn test_get_next_returns_highest_priority() {
@ -222,9 +214,7 @@ fn test_get_next_single_entry() {
assert_eq!(next.id, 42);
}
// =============================================================================
// Real-world Scenario Tests
// =============================================================================
#[test]
fn test_scenario_customer_support_queue() {
@ -314,9 +304,7 @@ fn test_estimated_wait_time() {
assert_eq!(estimated_wait, 10); // 2 people * 5 minutes each
}
// =============================================================================
// Edge Case Tests
// =============================================================================
#[test]
fn test_large_queue() {

View file

@ -5,9 +5,7 @@
use rhai::Engine;
// =============================================================================
// ABS Function Tests
// =============================================================================
#[test]
fn test_abs_positive() {
@ -46,9 +44,7 @@ fn test_abs_float() {
assert!((result - 3.14).abs() < f64::EPSILON);
}
// =============================================================================
// ROUND Function Tests
// =============================================================================
#[test]
fn test_round_up() {
@ -86,9 +82,7 @@ fn test_round_negative() {
assert_eq!(result, -4);
}
// =============================================================================
// INT / FIX Function Tests (Truncation)
// =============================================================================
#[test]
fn test_int_positive() {
@ -117,9 +111,7 @@ fn test_fix_alias() {
assert_eq!(result, 7);
}
// =============================================================================
// FLOOR / CEIL Function Tests
// =============================================================================
#[test]
fn test_floor_positive() {
@ -157,9 +149,7 @@ fn test_ceil_negative() {
assert_eq!(result, -3);
}
// =============================================================================
// MIN / MAX Function Tests
// =============================================================================
#[test]
fn test_max_basic() {
@ -224,9 +214,7 @@ fn test_min_negative() {
assert_eq!(result, -10);
}
// =============================================================================
// MOD Function Tests
// =============================================================================
#[test]
fn test_mod_basic() {
@ -255,9 +243,7 @@ fn test_mod_smaller_dividend() {
assert_eq!(result, 3);
}
// =============================================================================
// SGN Function Tests
// =============================================================================
#[test]
fn test_sgn_positive() {
@ -286,9 +272,7 @@ fn test_sgn_zero() {
assert_eq!(result, 0);
}
// =============================================================================
// SQRT / SQR Function Tests
// =============================================================================
#[test]
fn test_sqrt_perfect_square() {
@ -317,9 +301,7 @@ fn test_sqr_alias() {
assert!((result - 5.0).abs() < f64::EPSILON);
}
// =============================================================================
// POW Function Tests
// =============================================================================
#[test]
fn test_pow_basic() {
@ -348,9 +330,7 @@ fn test_pow_square_root() {
assert!((result - 3.0).abs() < 0.00001);
}
// =============================================================================
// LOG / LOG10 / EXP Function Tests
// =============================================================================
#[test]
fn test_log_e() {
@ -389,9 +369,7 @@ fn test_exp_one() {
assert!((result - std::f64::consts::E).abs() < 0.00001);
}
// =============================================================================
// Trigonometric Function Tests
// =============================================================================
#[test]
fn test_sin_zero() {
@ -429,9 +407,7 @@ fn test_pi_constant() {
assert!((result - std::f64::consts::PI).abs() < f64::EPSILON);
}
// =============================================================================
// VAL Function Tests (String to Number)
// =============================================================================
#[test]
fn test_val_integer() {
@ -488,9 +464,7 @@ fn test_val_with_whitespace() {
assert!((result - 42.0).abs() < f64::EPSILON);
}
// =============================================================================
// Combined Math Expression Tests
// =============================================================================
#[test]
fn test_combined_abs_sqrt() {

View file

@ -8,9 +8,7 @@
use rhai::Engine;
// =============================================================================
// INSTR Function Tests - Testing the actual behavior
// =============================================================================
#[test]
fn test_instr_finds_substring() {
@ -67,9 +65,7 @@ fn test_instr_case_sensitive() {
assert_eq!(result, 0); // Case sensitive, so not found
}
// =============================================================================
// UPPER / UCASE Function Tests
// =============================================================================
#[test]
fn test_upper_basic() {
@ -98,9 +94,7 @@ fn test_ucase_alias() {
assert_eq!(result, "TEST");
}
// =============================================================================
// LOWER / LCASE Function Tests
// =============================================================================
#[test]
fn test_lower_basic() {
@ -120,9 +114,7 @@ fn test_lcase_alias() {
assert_eq!(result, "test");
}
// =============================================================================
// LEN Function Tests
// =============================================================================
#[test]
fn test_len_basic() {
@ -151,9 +143,7 @@ fn test_len_with_spaces() {
assert_eq!(result, 11);
}
// =============================================================================
// TRIM / LTRIM / RTRIM Function Tests
// =============================================================================
#[test]
fn test_trim_both_sides() {
@ -182,9 +172,7 @@ fn test_rtrim() {
assert_eq!(result, " hello");
}
// =============================================================================
// LEFT Function Tests
// =============================================================================
#[test]
fn test_left_basic() {
@ -222,9 +210,7 @@ fn test_left_zero() {
assert_eq!(result, "");
}
// =============================================================================
// RIGHT Function Tests
// =============================================================================
#[test]
fn test_right_basic() {
@ -260,9 +246,7 @@ fn test_right_exceeds_length() {
assert_eq!(result, "Hi");
}
// =============================================================================
// MID Function Tests
// =============================================================================
#[test]
fn test_mid_with_length() {
@ -294,9 +278,7 @@ fn test_mid_one_based_index() {
assert_eq!(result, "C");
}
// =============================================================================
// REPLACE Function Tests
// =============================================================================
#[test]
fn test_replace_basic() {
@ -333,9 +315,7 @@ fn test_replace_not_found() {
assert_eq!(result, "Hello");
}
// =============================================================================
// IS_NUMERIC Function Tests
// =============================================================================
#[test]
fn test_is_numeric_integer() {
@ -397,9 +377,7 @@ fn test_is_numeric_empty() {
assert!(!result);
}
// =============================================================================
// Combined Expression Tests
// =============================================================================
#[test]
fn test_combined_string_operations() {