mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 23:10:11 +00:00
* fix(ci): use explicit features in WASM WIT compat test to avoid sqlite3 symbol conflicts The `import` feature (added in #903) brings in `rusqlite[bundled]` which conflicts with `libsql-ffi` — both bundle SQLite C code, causing duplicate symbol linker errors. Use explicit features matching the test matrix instead of `--all-features`. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: replace rusqlite with libsql in import module to fix sqlite3 symbol conflict The `import` feature used `rusqlite[bundled]` which bundled its own SQLite C code, conflicting with `libsql-ffi` (also bundles SQLite). This caused duplicate `sqlite3_*` symbol linker errors when both features were enabled via `--all-features`. Replace `rusqlite` with `libsql` (already a dependency) in the import reader. The `import` feature now implies `libsql`. This eliminates the duplicate symbol conflict and allows `--all-features` to compile cleanly. Also restores `--all-features` in the WASM WIT compat CI test (now safe) and converts all import test helpers from rusqlite to libsql. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: apply cargo fmt formatting fixes Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
474 lines
18 KiB
Rust
474 lines
18 KiB
Rust
//! Error handling and edge case tests for OpenClaw import.
|
|
//!
|
|
//! These tests verify proper error handling for:
|
|
//! - Missing/corrupt files
|
|
//! - Invalid configurations
|
|
//! - Database corruption
|
|
//! - Permission issues
|
|
//! - Edge cases in data
|
|
|
|
#![cfg(feature = "import")]
|
|
|
|
#[cfg(feature = "import")]
|
|
mod error_handling_tests {
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
|
|
use ironclaw::import::ImportError;
|
|
use ironclaw::import::openclaw::reader::OpenClawReader;
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// Missing Directory Tests
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_error_nonexistent_openclaw_directory() {
|
|
let nonexistent = PathBuf::from("/nonexistent/path/openclaw");
|
|
let result = OpenClawReader::new(&nonexistent);
|
|
|
|
assert!(result.is_err());
|
|
if let Err(e) = result {
|
|
match e {
|
|
ImportError::NotFound { .. } => (), // Expected
|
|
_ => panic!("Expected NotFound, got: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_empty_openclaw_directory() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let result = OpenClawReader::new(temp_dir.path());
|
|
|
|
// Should succeed (directory exists)
|
|
assert!(result.is_ok());
|
|
|
|
let reader = result.unwrap();
|
|
let config_result = reader.read_config();
|
|
|
|
// But reading config should fail
|
|
assert!(config_result.is_err());
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// Config File Errors
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_error_missing_openclaw_json() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let result = reader.read_config();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_invalid_json5_syntax() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
// Invalid JSON5: missing closing brace
|
|
let bad_config = r#"{ llm: { provider: "openai" }"#;
|
|
std::fs::write(openclaw_path.join("openclaw.json"), bad_config).expect("write failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let result = reader.read_config();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_truncated_json5() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
// Truncated JSON5
|
|
std::fs::write(openclaw_path.join("openclaw.json"), "{").expect("write failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let result = reader.read_config();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_empty_openclaw_json() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
// Empty file
|
|
std::fs::write(openclaw_path.join("openclaw.json"), "").expect("write failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let result = reader.read_config();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// SQLite Database Errors
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
#[tokio::test]
|
|
async fn test_error_corrupt_sqlite_file() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
// Write invalid SQLite data
|
|
std::fs::write(
|
|
agents_dir.join("bad.sqlite"),
|
|
"this is definitely not a sqlite database",
|
|
)
|
|
.expect("write failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
assert_eq!(dbs.len(), 1);
|
|
|
|
// But reading should fail
|
|
let result = reader.read_memory_chunks(&dbs[0].1).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_error_missing_chunks_table() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("no_chunks.sqlite");
|
|
|
|
// Create valid SQLite but without chunks table
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
assert_eq!(dbs.len(), 1);
|
|
|
|
// Should fail: chunks table doesn't exist
|
|
let result = reader.read_memory_chunks(&dbs[0].1).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_error_missing_conversations_table() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("no_conversations.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
// Only create chunks table, not conversations
|
|
conn.execute(
|
|
"CREATE TABLE chunks (id TEXT, path TEXT, content TEXT, embedding BLOB, chunk_index INTEGER)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
assert_eq!(dbs.len(), 1);
|
|
|
|
// Should fail: conversations table doesn't exist
|
|
let result = reader.read_conversations(&dbs[0].1).await;
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// Edge Cases
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_case_empty_chunks_table() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("empty.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE chunks (id TEXT, path TEXT, content TEXT, embedding BLOB, chunk_index INTEGER)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
|
|
// Should succeed but return empty list
|
|
let chunks = reader
|
|
.read_memory_chunks(&dbs[0].1)
|
|
.await
|
|
.expect("read chunks failed");
|
|
assert_eq!(chunks.len(), 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_case_empty_conversations_table() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("empty_conv.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE conversations (id TEXT, channel TEXT, created_at TEXT)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
conn.execute(
|
|
"CREATE TABLE messages (id TEXT, conversation_id TEXT, role TEXT, content TEXT, created_at TEXT)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
|
|
// Should succeed but return empty list
|
|
let conversations = reader
|
|
.read_conversations(&dbs[0].1)
|
|
.await
|
|
.expect("read conversations failed");
|
|
assert_eq!(conversations.len(), 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_case_very_large_content() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("large.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE chunks (id TEXT, path TEXT, content TEXT, embedding BLOB, chunk_index INTEGER)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
// Insert very large content (1MB)
|
|
let large_content = "x".repeat(1024 * 1024);
|
|
conn.execute(
|
|
"INSERT INTO chunks VALUES (?, ?, ?, ?, ?)",
|
|
libsql::params!["id1", "path", large_content, libsql::Value::Null, 0i64],
|
|
)
|
|
.await
|
|
.expect("insert failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
|
|
// Should still succeed
|
|
let chunks = reader
|
|
.read_memory_chunks(&dbs[0].1)
|
|
.await
|
|
.expect("read chunks failed");
|
|
assert_eq!(chunks.len(), 1);
|
|
assert_eq!(chunks[0].content.len(), 1024 * 1024);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_case_special_characters_in_content() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("special.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE chunks (id TEXT, path TEXT, content TEXT, embedding BLOB, chunk_index INTEGER)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
// Insert content with special characters
|
|
let special_content = "Content with emoji \u{1f680} and UTF-8: \u{4e2d}\u{6587}, \u{0627}\u{0644}\u{0639}\u{0631}\u{0628}\u{064a}\u{0629}, \u{03b5}\u{03bb}\u{03bb}\u{03b7}\u{03bd}\u{03b9}\u{03ba}\u{03ac}";
|
|
conn.execute(
|
|
"INSERT INTO chunks VALUES (?, ?, ?, ?, ?)",
|
|
libsql::params!["id1", "path", special_content, libsql::Value::Null, 0i64],
|
|
)
|
|
.await
|
|
.expect("insert failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
|
|
// Should handle special characters
|
|
let chunks = reader
|
|
.read_memory_chunks(&dbs[0].1)
|
|
.await
|
|
.expect("read chunks failed");
|
|
assert_eq!(chunks.len(), 1);
|
|
assert!(chunks[0].content.contains("\u{1f680}"));
|
|
assert!(chunks[0].content.contains("\u{4e2d}\u{6587}"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_edge_case_null_values_in_fields() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let agents_dir = openclaw_path.join("agents");
|
|
std::fs::create_dir_all(&agents_dir).expect("mkdir failed");
|
|
|
|
let db_path = agents_dir.join("nulls.sqlite");
|
|
|
|
let db = libsql::Builder::new_local(&db_path)
|
|
.build()
|
|
.await
|
|
.expect("db creation failed");
|
|
let conn = db.connect().expect("connect failed");
|
|
conn.execute(
|
|
"CREATE TABLE conversations (id TEXT, channel TEXT, created_at TEXT)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
conn.execute(
|
|
"CREATE TABLE messages (id TEXT, conversation_id TEXT, role TEXT, content TEXT, created_at TEXT)",
|
|
(),
|
|
)
|
|
.await
|
|
.expect("create table failed");
|
|
|
|
// Insert conversation with NULL created_at
|
|
conn.execute(
|
|
"INSERT INTO conversations VALUES (?, ?, ?)",
|
|
libsql::params!["conv1", "telegram", libsql::Value::Null],
|
|
)
|
|
.await
|
|
.expect("insert failed");
|
|
|
|
// Insert message with NULL created_at
|
|
conn.execute(
|
|
"INSERT INTO messages VALUES (?, ?, ?, ?, ?)",
|
|
libsql::params!["msg1", "conv1", "user", "hello", libsql::Value::Null],
|
|
)
|
|
.await
|
|
.expect("insert failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let dbs = reader.list_agent_dbs().expect("list agent dbs failed");
|
|
|
|
// Should handle NULL timestamps gracefully
|
|
let conversations = reader
|
|
.read_conversations(&dbs[0].1)
|
|
.await
|
|
.expect("read conversations failed");
|
|
assert_eq!(conversations.len(), 1);
|
|
assert!(conversations[0].created_at.is_none());
|
|
assert!(conversations[0].messages[0].created_at.is_none());
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
// Workspace File Errors
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn test_error_workspace_not_directory() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
// Create "workspace" as a file, not a directory
|
|
std::fs::write(openclaw_path.join("workspace"), "not a directory").expect("write failed");
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
// Should handle gracefully (no files found)
|
|
let count = reader
|
|
.list_workspace_files()
|
|
.expect("list workspace files failed");
|
|
assert_eq!(count, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_edge_case_many_markdown_files() {
|
|
let temp_dir = TempDir::new().expect("temp dir creation failed");
|
|
let openclaw_path = temp_dir.path().to_path_buf();
|
|
|
|
let workspace_dir = openclaw_path.join("workspace");
|
|
std::fs::create_dir_all(&workspace_dir).expect("mkdir failed");
|
|
|
|
// Create 100 markdown files
|
|
for i in 0..100 {
|
|
std::fs::write(workspace_dir.join(format!("doc_{}.md", i)), "content")
|
|
.expect("write failed");
|
|
}
|
|
|
|
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
|
|
|
|
let count = reader
|
|
.list_workspace_files()
|
|
.expect("list workspace files failed");
|
|
assert_eq!(count, 100);
|
|
}
|
|
}
|