use std::path::PathBuf;
use super::index::ensure_memory_index;
use super::paths::encode_project_path;
use super::runtime::{enforce_native_memory_limit, env_value_enabled};
fn unique_temp_dir(name: &str) -> PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock should be after unix epoch")
.as_nanos();
std::env::temp_dir().join(format!("remem-{}-{}-{}", name, std::process::id(), nanos))
}
#[test]
fn encode_project_path_replaces_slashes_after_canonicalize() {
let dir = unique_temp_dir("claude-memory-path");
std::fs::create_dir_all(&dir).expect("temp dir should create");
let encoded = encode_project_path(dir.to_str().expect("temp path should be utf-8"));
let canonical =
crate::db::canonical_project_path(dir.to_str().expect("temp path should be utf-8"));
let expected = canonical.to_string_lossy().replace('/', "-");
assert_eq!(encoded, expected);
std::fs::remove_dir_all(&dir).expect("temp dir should clean up");
}
#[test]
fn ensure_memory_index_is_idempotent() {
let dir = unique_temp_dir("claude-memory-index-idempotent");
std::fs::create_dir_all(&dir).expect("temp dir should create");
let index_path = dir.join("MEMORY.md");
std::fs::write(&index_path, "# Memory Index\n").expect("index should write");
ensure_memory_index(&dir).expect("first ensure should succeed");
ensure_memory_index(&dir).expect("second ensure should succeed");
let content = std::fs::read_to_string(&index_path).expect("index should read");
assert_eq!(content.matches("remem_sessions.md").count(), 1);
std::fs::remove_dir_all(&dir).expect("temp dir should clean up");
}
#[test]
fn ensure_memory_index_creates_new_file_when_missing() {
let dir = unique_temp_dir("claude-memory-index-create");
std::fs::create_dir_all(&dir).expect("temp dir should create");
ensure_memory_index(&dir).expect("ensure should succeed");
let content = std::fs::read_to_string(dir.join("MEMORY.md")).expect("index should read");
assert!(content.contains("# Memory Index"));
assert!(content.contains("remem_sessions.md"));
std::fs::remove_dir_all(&dir).expect("temp dir should clean up");
}
#[test]
fn native_memory_limit_caps_output_without_breaking_utf8() {
let content = format!(
"---\nname: remem_sessions\n---\n\n{}\n",
"关键决策和最近会话摘要".repeat(80)
);
let (capped, truncated) = enforce_native_memory_limit(&content, 512);
assert!(truncated);
assert!(capped.len() <= 512, "capped length was {}", capped.len());
assert!(capped.is_char_boundary(capped.len()));
assert!(capped.contains("Truncated by remem native memory guard"));
}
#[test]
fn native_memory_disable_env_accepts_explicit_truthy_values() {
for value in ["1", "true", "TRUE", "yes", "on"] {
assert!(env_value_enabled(value), "{value}");
}
for value in ["", "0", "false", "off", "disabled"] {
assert!(!env_value_enabled(value), "{value}");
}
}
#[test]
fn native_memory_sessions_exclude_synthetic_rollup_titles() -> anyhow::Result<()> {
let conn = rusqlite::Connection::open_in_memory()?;
crate::migrate::run_migrations(&conn)?;
conn.execute(
"INSERT INTO session_summaries
(memory_session_id, project, request, completed, decisions,
created_at_epoch, session_row_id, covered_from_event_id,
covered_to_event_id)
VALUES
('rollup-synthetic', '/repo', 'Captured event range 1..3',
'synthetic range fallback', '', 10, 1, 1, 3),
('rollup-semantic', '/repo', 'Retire legacy Summary writer',
'semantic rollup summary', 'SessionRollup owns structured fields',
11, 2, 4, 6),
('rollup-structured-fallback', '/repo', 'Captured event range 7..9',
'structured fallback summary', 'Fallback request keeps decisions',
12, 3, 7, 9)",
[],
)?;
let sessions = super::runtime::load_recent_sessions(&conn, "/repo")?;
let content =
super::render::render_memory_content(&sessions).expect("semantic session should render");
assert!(content.contains("Retire legacy Summary writer"));
assert!(content.contains("SessionRollup owns structured fields"));
assert!(content.contains("Fallback request keeps decisions"));
assert!(!content.contains("Captured event range 1..3"));
assert!(!content.contains("Captured event range 7..9"));
assert!(!content.contains("synthetic range fallback"));
Ok(())
}