Files
zclaw_openfang/crates/zclaw-growth/tests/integration_test.rs
iven b7f3d94950
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(presentation): 修复 presentation 模块类型错误和语法问题
- 创建 types.ts 定义完整的类型系统
- 重写 DocumentRenderer.tsx 修复语法错误
- 重写 QuizRenderer.tsx 修复语法错误
- 重写 PresentationContainer.tsx 添加类型守卫
- 重写 TypeSwitcher.tsx 修复类型引用
- 更新 index.ts 移除不存在的 ChartRenderer 导出

审计结果:
- 类型检查: 通过
- 单元测试: 222 passed
- 构建: 成功
2026-03-26 17:19:28 +08:00

413 lines
12 KiB
Rust

//! Integration tests for ZCLAW Growth System
//!
//! Tests the complete flow: store → find → inject
use std::sync::Arc;
use zclaw_growth::{
FindOptions, MemoryEntry, MemoryRetriever, MemoryType, PromptInjector,
RetrievalConfig, RetrievalResult, SqliteStorage, VikingAdapter,
};
use zclaw_types::AgentId;
/// Test complete memory lifecycle
#[tokio::test]
async fn test_memory_lifecycle() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
// Create agent ID and use its string form for storage
let agent_id = AgentId::new();
let agent_str = agent_id.to_string();
// 1. Store a preference
let pref = MemoryEntry::new(
&agent_str,
MemoryType::Preference,
"communication-style",
"用户偏好简洁的回复,不喜欢冗长的解释".to_string(),
)
.with_keywords(vec!["简洁".to_string(), "沟通风格".to_string()])
.with_importance(8);
adapter.store(&pref).await.unwrap();
// 2. Store knowledge
let knowledge = MemoryEntry::new(
&agent_str,
MemoryType::Knowledge,
"rust-expertise",
"用户是 Rust 开发者,熟悉 async/await 和 trait 系统".to_string(),
)
.with_keywords(vec!["Rust".to_string(), "开发者".to_string()]);
adapter.store(&knowledge).await.unwrap();
// 3. Store experience
let experience = MemoryEntry::new(
&agent_str,
MemoryType::Experience,
"browser-skill",
"浏览器技能在搜索技术文档时效果很好".to_string(),
)
.with_keywords(vec!["浏览器".to_string(), "技能".to_string()]);
adapter.store(&experience).await.unwrap();
// 4. Retrieve memories - directly from adapter first
let direct_results = adapter
.find(
"Rust",
FindOptions {
scope: Some(format!("agent://{}", agent_str)),
limit: Some(10),
min_similarity: Some(0.1),
},
)
.await
.unwrap();
println!("Direct find results: {:?}", direct_results.len());
let retriever = MemoryRetriever::new(adapter.clone());
// Use lower similarity threshold for testing
let config = RetrievalConfig {
min_similarity: 0.1,
..RetrievalConfig::default()
};
let retriever = retriever.with_config(config);
let result = retriever
.retrieve(&agent_id, "Rust 编程")
.await
.unwrap();
println!("Knowledge results: {:?}", result.knowledge.len());
println!("Preferences results: {:?}", result.preferences.len());
println!("Experience results: {:?}", result.experience.len());
// Should find the knowledge entry
assert!(!result.knowledge.is_empty(), "Expected to find knowledge entries but found none. Direct results: {}", direct_results.len());
assert!(result.knowledge[0].content.contains("Rust"));
// 5. Inject into prompt
let injector = PromptInjector::new();
let base_prompt = "你是一个有帮助的 AI 助手。";
let enhanced = injector.inject_with_format(base_prompt, &result);
// Enhanced prompt should contain memory context
assert!(enhanced.len() > base_prompt.len());
}
/// Test semantic search ranking
#[tokio::test]
async fn test_semantic_search_ranking() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage.clone()));
// Store multiple entries with different relevance
let entries = vec![
MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
"rust-basics",
"Rust 是一门系统编程语言,注重安全性和性能".to_string(),
)
.with_keywords(vec!["Rust".to_string(), "系统编程".to_string()]),
MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
"python-basics",
"Python 是一门高级编程语言,易于学习".to_string(),
)
.with_keywords(vec!["Python".to_string(), "高级语言".to_string()]),
MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
"rust-async",
"Rust 的 async/await 语法用于异步编程".to_string(),
)
.with_keywords(vec!["Rust".to_string(), "async".to_string(), "异步".to_string()]),
];
for entry in &entries {
adapter.store(entry).await.unwrap();
}
// Search for "Rust 异步编程"
let results = adapter
.find(
"Rust 异步编程",
FindOptions {
scope: Some("agent://agent-1".to_string()),
limit: Some(10),
min_similarity: Some(0.1),
},
)
.await
.unwrap();
// Rust async entry should rank highest
assert!(!results.is_empty());
assert!(results[0].content.contains("async") || results[0].content.contains("Rust"));
}
/// Test memory importance and access count
#[tokio::test]
async fn test_importance_and_access() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage.clone()));
// Create entries with different importance
let high_importance = MemoryEntry::new(
"agent-1",
MemoryType::Preference,
"critical",
"这是非常重要的偏好".to_string(),
)
.with_importance(10);
let low_importance = MemoryEntry::new(
"agent-1",
MemoryType::Preference,
"minor",
"这是不太重要的偏好".to_string(),
)
.with_importance(2);
adapter.store(&high_importance).await.unwrap();
adapter.store(&low_importance).await.unwrap();
// Access the low importance one multiple times
for _ in 0..5 {
let _ = adapter.get(&low_importance.uri).await;
}
// Search should consider both importance and access count
let results = adapter
.find(
"偏好",
FindOptions {
scope: Some("agent://agent-1".to_string()),
limit: Some(10),
min_similarity: None,
},
)
.await
.unwrap();
assert_eq!(results.len(), 2);
}
/// Test prompt injection with token budget
#[tokio::test]
async fn test_prompt_injection_token_budget() {
let mut result = RetrievalResult::default();
// Add memories that exceed budget
for i in 0..10 {
result.preferences.push(
MemoryEntry::new(
"agent-1",
MemoryType::Preference,
&format!("pref-{}", i),
"这是一个很长的偏好描述,用于测试 token 预算控制功能。".repeat(5),
),
);
}
result.total_tokens = result.calculate_tokens();
// Budget is 500 tokens by default
let injector = PromptInjector::new();
let base = "Base prompt";
let enhanced = injector.inject_with_format(base, &result);
// Should include memory context
assert!(enhanced.len() > base.len());
}
/// Test metadata storage
#[tokio::test]
async fn test_metadata_operations() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
// Store metadata using typed API
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Config {
version: String,
auto_extract: bool,
}
let config = Config {
version: "1.0.0".to_string(),
auto_extract: true,
};
adapter.store_metadata("agent-config", &config).await.unwrap();
// Retrieve metadata
let retrieved: Option<Config> = adapter.get_metadata("agent-config").await.unwrap();
assert!(retrieved.is_some());
let parsed = retrieved.unwrap();
assert_eq!(parsed.version, "1.0.0");
assert_eq!(parsed.auto_extract, true);
}
/// Test memory deletion and cleanup
#[tokio::test]
async fn test_memory_deletion() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
let entry = MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
"temp",
"Temporary knowledge".to_string(),
);
adapter.store(&entry).await.unwrap();
// Verify stored
let retrieved = adapter.get(&entry.uri).await.unwrap();
assert!(retrieved.is_some());
// Delete
adapter.delete(&entry.uri).await.unwrap();
// Verify deleted
let retrieved = adapter.get(&entry.uri).await.unwrap();
assert!(retrieved.is_none());
// Verify not in search results
let results = adapter
.find(
"Temporary",
FindOptions {
scope: Some("agent://agent-1".to_string()),
limit: Some(10),
min_similarity: None,
},
)
.await
.unwrap();
assert!(results.is_empty());
}
/// Test cross-agent isolation
#[tokio::test]
async fn test_agent_isolation() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
// Store memories for different agents
let agent1_memory = MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
"secret",
"Agent 1 的秘密信息".to_string(),
);
let agent2_memory = MemoryEntry::new(
"agent-2",
MemoryType::Knowledge,
"secret",
"Agent 2 的秘密信息".to_string(),
);
adapter.store(&agent1_memory).await.unwrap();
adapter.store(&agent2_memory).await.unwrap();
// Agent 1 should only see its own memories
let results = adapter
.find(
"秘密",
FindOptions {
scope: Some("agent://agent-1".to_string()),
limit: Some(10),
min_similarity: None,
},
)
.await
.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].content.contains("Agent 1"));
// Agent 2 should only see its own memories
let results = adapter
.find(
"秘密",
FindOptions {
scope: Some("agent://agent-2".to_string()),
limit: Some(10),
min_similarity: None,
},
)
.await
.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].content.contains("Agent 2"));
}
/// Test Chinese text handling
#[tokio::test]
async fn test_chinese_text_handling() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
let entry = MemoryEntry::new(
"中文测试",
MemoryType::Knowledge,
"中文知识",
"这是一个中文测试,包含关键词:人工智能、机器学习、深度学习。".to_string(),
)
.with_keywords(vec!["人工智能".to_string(), "机器学习".to_string()]);
adapter.store(&entry).await.unwrap();
// Search with Chinese query
let results = adapter
.find(
"人工智能",
FindOptions {
scope: Some("agent://中文测试".to_string()),
limit: Some(10),
min_similarity: Some(0.1),
},
)
.await
.unwrap();
assert!(!results.is_empty());
assert!(results[0].content.contains("人工智能"));
}
/// Test find by prefix
#[tokio::test]
async fn test_find_by_prefix() {
let storage = Arc::new(SqliteStorage::in_memory().await);
let adapter = Arc::new(VikingAdapter::new(storage));
// Store multiple entries under same agent
for i in 0..5 {
let entry = MemoryEntry::new(
"agent-1",
MemoryType::Knowledge,
&format!("topic-{}", i),
format!("Content for topic {}", i),
);
adapter.store(&entry).await.unwrap();
}
// Find all entries for agent-1
let results = adapter
.find_by_prefix("agent://agent-1")
.await
.unwrap();
assert_eq!(results.len(), 5);
}