fix(ci): WASM WIT compat sqlite3 duplicate symbol conflict (#953)

* 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]>
This commit is contained in:
Henry Park
2026-03-11 11:48:24 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 6b841bb817
commit fe82469904
9 changed files with 435 additions and 366 deletions
+88 -56
View File
@@ -112,8 +112,8 @@ mod error_handling_tests {
// SQLite Database Errors
// ────────────────────────────────────────────────────────────────────
#[test]
fn test_error_corrupt_sqlite_file() {
#[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();
@@ -133,12 +133,12 @@ mod error_handling_tests {
assert_eq!(dbs.len(), 1);
// But reading should fail
let result = reader.read_memory_chunks(&dbs[0].1);
let result = reader.read_memory_chunks(&dbs[0].1).await;
assert!(result.is_err());
}
#[test]
fn test_error_missing_chunks_table() {
#[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();
@@ -148,14 +148,17 @@ mod error_handling_tests {
let db_path = agents_dir.join("no_chunks.sqlite");
// Create valid SQLite but without chunks table
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -163,12 +166,12 @@ mod error_handling_tests {
assert_eq!(dbs.len(), 1);
// Should fail: chunks table doesn't exist
let result = reader.read_memory_chunks(&dbs[0].1);
let result = reader.read_memory_chunks(&dbs[0].1).await;
assert!(result.is_err());
}
#[test]
fn test_error_missing_conversations_table() {
#[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();
@@ -177,15 +180,18 @@ mod error_handling_tests {
let db_path = agents_dir.join("no_conversations.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -193,7 +199,7 @@ mod error_handling_tests {
assert_eq!(dbs.len(), 1);
// Should fail: conversations table doesn't exist
let result = reader.read_conversations(&dbs[0].1);
let result = reader.read_conversations(&dbs[0].1).await;
assert!(result.is_err());
}
@@ -201,8 +207,8 @@ mod error_handling_tests {
// Edge Cases
// ────────────────────────────────────────────────────────────────────
#[test]
fn test_edge_case_empty_chunks_table() {
#[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();
@@ -211,14 +217,17 @@ mod error_handling_tests {
let db_path = agents_dir.join("empty.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -227,12 +236,13 @@ mod error_handling_tests {
// 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);
}
#[test]
fn test_edge_case_empty_conversations_table() {
#[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();
@@ -241,19 +251,23 @@ mod error_handling_tests {
let db_path = agents_dir.join("empty_conv.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -262,12 +276,13 @@ mod error_handling_tests {
// Should succeed but return empty list
let conversations = reader
.read_conversations(&dbs[0].1)
.await
.expect("read conversations failed");
assert_eq!(conversations.len(), 0);
}
#[test]
fn test_edge_case_very_large_content() {
#[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();
@@ -276,22 +291,26 @@ mod error_handling_tests {
let db_path = agents_dir.join("large.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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 (?, ?, ?, ?, ?)",
rusqlite::params!["id1", "path", large_content, None::<Vec<u8>>, 0],
libsql::params!["id1", "path", large_content, libsql::Value::Null, 0i64],
)
.await
.expect("insert failed");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -300,13 +319,14 @@ mod error_handling_tests {
// 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);
}
#[test]
fn test_edge_case_special_characters_in_content() {
#[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();
@@ -315,22 +335,26 @@ mod error_handling_tests {
let db_path = agents_dir.join("special.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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 🚀 and UTF-8: 中文, العربية, ελληνικά";
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 (?, ?, ?, ?, ?)",
rusqlite::params!["id1", "path", special_content, None::<Vec<u8>>, 0],
libsql::params!["id1", "path", special_content, libsql::Value::Null, 0i64],
)
.await
.expect("insert failed");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -339,14 +363,15 @@ mod error_handling_tests {
// 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("🚀"));
assert!(chunks[0].content.contains("中文"));
assert!(chunks[0].content.contains("\u{1f680}"));
assert!(chunks[0].content.contains("\u{4e2d}\u{6587}"));
}
#[test]
fn test_edge_case_null_values_in_fields() {
#[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();
@@ -355,33 +380,39 @@ mod error_handling_tests {
let db_path = agents_dir.join("nulls.sqlite");
use rusqlite::Connection;
let conn = Connection::open(&db_path).expect("db creation failed");
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 (?, ?, ?)",
rusqlite::params!["conv1", "telegram", None::<String>],
libsql::params!["conv1", "telegram", libsql::Value::Null],
)
.await
.expect("insert failed");
// Insert message with NULL created_at
conn.execute(
"INSERT INTO messages VALUES (?, ?, ?, ?, ?)",
rusqlite::params!["msg1", "conv1", "user", "hello", None::<String>],
libsql::params!["msg1", "conv1", "user", "hello", libsql::Value::Null],
)
.await
.expect("insert failed");
drop(conn);
let reader = OpenClawReader::new(&openclaw_path).expect("reader creation failed");
@@ -390,6 +421,7 @@ mod error_handling_tests {
// 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());