Files
MyAiDesk/nomifun-tauri/crates/agent/nomi-memory/tests/e2e_integration.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

216 lines
7.4 KiB
Rust

// End-to-end integration tests for the memory system (TC-8).
//
// These tests exercise the full memory lifecycle across multiple modules,
// verifying that all components work together correctly.
use std::fs;
use nomi_memory::index;
use nomi_memory::paths;
use nomi_memory::prompt::build_memory_prompt;
use nomi_memory::store;
use nomi_memory::types::{MemoryEntry, MemoryType};
// ===========================================================================
// TC-8.1: Complete memory lifecycle
// ===========================================================================
#[test]
fn tc_8_1_complete_memory_lifecycle() {
let tmp = tempfile::tempdir().unwrap();
let mem_dir = tmp.path().join("memory");
// 1. Ensure memory directory exists
paths::ensure_memory_dir(&mem_dir).unwrap();
assert!(mem_dir.is_dir());
// 2. Write a feedback-type memory
let entry = MemoryEntry::build(
"test policy",
"integration tests must hit real DB",
MemoryType::Feedback,
"Never mock the database in integration tests.\n\n\
**Why:** mocked tests once passed but prod migration failed.\n\n\
**How to apply:** use testcontainers for all DB tests.",
);
let written_path = store::write_memory(&mem_dir, &entry).unwrap();
assert!(written_path.exists());
// 3. Append index entry to MEMORY.md
let index_path = paths::memory_entrypoint(&mem_dir);
let filename = written_path.file_name().unwrap().to_str().unwrap();
index::append_index_entry(
&index_path,
"Test Policy",
filename,
"integration tests must hit real DB",
)
.unwrap();
// 4. Scan directory — the memory should appear
let headers = store::scan_memory_files(&mem_dir).unwrap();
assert_eq!(
headers.len(),
1,
"should find exactly 1 memory file (MEMORY.md excluded)"
);
assert_eq!(headers[0].filename, filename);
assert_eq!(headers[0].memory_type, Some(MemoryType::Feedback));
assert_eq!(
headers[0].description.as_deref(),
Some("integration tests must hit real DB")
);
// 5. Build prompt — should include MEMORY.md content
let prompt = build_memory_prompt(&mem_dir);
assert!(
prompt.contains(filename),
"prompt should reference the memory file"
);
assert!(
prompt.contains("integration tests must hit real DB"),
"prompt should contain the index summary"
);
// 6. Read back the memory file — verify content integrity
let read_back = store::read_memory(&written_path).unwrap();
assert_eq!(read_back.frontmatter.name.as_deref(), Some("test policy"));
assert_eq!(
read_back.frontmatter.memory_type,
Some(MemoryType::Feedback)
);
assert!(read_back.content.contains("testcontainers"));
// 7. Delete the memory file
store::delete_memory(&written_path).unwrap();
assert!(!written_path.exists());
// 8. Re-scan — should be empty
let headers_after = store::scan_memory_files(&mem_dir).unwrap();
assert!(
headers_after.is_empty(),
"should find no memory files after deletion"
);
}
// ===========================================================================
// TC-8.2: Chinese content memory
// ===========================================================================
#[test]
fn tc_8_2_chinese_content_roundtrip() {
let tmp = tempfile::tempdir().unwrap();
let mem_dir = tmp.path().join("memory");
paths::ensure_memory_dir(&mem_dir).unwrap();
let entry = MemoryEntry::build(
"用户角色",
"资深后端工程师",
MemoryType::User,
"用户是一位有十年经验的后端工程师,熟悉 Rust 和 Go。\n\
偏好函数式编程风格,不喜欢过度抽象。",
);
// Write
let path = store::write_memory(&mem_dir, &entry).unwrap();
assert!(path.exists());
// Read back — Chinese content should be intact
let read_back = store::read_memory(&path).unwrap();
assert_eq!(read_back.frontmatter.name.as_deref(), Some("用户角色"));
assert_eq!(
read_back.frontmatter.description.as_deref(),
Some("资深后端工程师")
);
assert_eq!(read_back.frontmatter.memory_type, Some(MemoryType::User));
assert!(read_back.content.contains("十年经验"));
assert!(read_back.content.contains("函数式编程"));
// Scan — header should preserve Chinese description
let headers = store::scan_memory_files(&mem_dir).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].description.as_deref(), Some("资深后端工程师"));
// Index — append Chinese title and verify
let index_path = paths::memory_entrypoint(&mem_dir);
let filename = path.file_name().unwrap().to_str().unwrap();
index::append_index_entry(&index_path, "用户角色", filename, "资深后端工程师").unwrap();
let index_content = fs::read_to_string(&index_path).unwrap();
assert!(index_content.contains("用户角色"));
assert!(index_content.contains("资深后端工程师"));
// Prompt — should include Chinese index content
let prompt = build_memory_prompt(&mem_dir);
assert!(prompt.contains("用户角色"));
assert!(prompt.contains("资深后端工程师"));
}
// ===========================================================================
// TC-8.3: Special character handling
// ===========================================================================
#[test]
fn tc_8_3_special_characters_in_name() {
let tmp = tempfile::tempdir().unwrap();
let mem_dir = tmp.path().join("memory");
paths::ensure_memory_dir(&mem_dir).unwrap();
// Name with special characters: spaces, slashes, colons, emoji
let entry = MemoryEntry::build(
"My Role / Senior: 🚀",
"role with special chars",
MemoryType::User,
"Body with special chars: <tag>, \"quotes\", 'apostrophes' & ampersands",
);
let path = store::write_memory(&mem_dir, &entry).unwrap();
// Filename should be safe (no slashes, colons, etc.)
let filename = path.file_name().unwrap().to_str().unwrap();
assert!(
!filename.contains('/'),
"filename should not contain slash: {filename}"
);
assert!(
!filename.contains(':'),
"filename should not contain colon: {filename}"
);
assert!(
filename.ends_with(".md"),
"filename should end with .md: {filename}"
);
// Content should round-trip correctly
let read_back = store::read_memory(&path).unwrap();
assert!(read_back.content.contains("<tag>"));
assert!(read_back.content.contains("\"quotes\""));
assert!(read_back.content.contains("& ampersands"));
}
#[test]
fn tc_8_3_name_with_only_special_chars() {
let tmp = tempfile::tempdir().unwrap();
let mem_dir = tmp.path().join("memory");
paths::ensure_memory_dir(&mem_dir).unwrap();
// Edge case: name is entirely special characters / non-ASCII
let entry = MemoryEntry::build(
"🔥💡✨",
"emoji only name",
MemoryType::Feedback,
"Some body",
);
let path = store::write_memory(&mem_dir, &entry).unwrap();
assert!(path.exists());
// Should still produce a valid filename (hash fallback)
let filename = path.file_name().unwrap().to_str().unwrap();
assert!(filename.ends_with(".md"));
// Content should round-trip
let read_back = store::read_memory(&path).unwrap();
assert_eq!(read_back.content, "Some body");
}