remem-ai 0.6.42

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use super::*;

fn with_migration_config_path<T>(path: &std::path::Path, f: impl FnOnce() -> T) -> T {
    let _guard = TEST_ENV_LOCK.lock().expect("env lock should acquire");
    let old = std::env::var("REMEM_CONFIG").ok();
    unsafe { std::env::set_var("REMEM_CONFIG", path) };
    let result = f();
    match old {
        Some(value) => unsafe { std::env::set_var("REMEM_CONFIG", value) },
        None => unsafe { std::env::remove_var("REMEM_CONFIG") },
    }
    result
}

fn migration_temp_config_path(label: &str) -> PathBuf {
    std::env::temp_dir().join(format!(
        "remem-{label}-{}-{}.toml",
        std::process::id(),
        chrono::Utc::now().timestamp_nanos_opt().unwrap_or_default()
    ))
}

fn legacy_claude_gate_config() -> &'static str {
    "[memory_ai.hosts.claude-code]\nmemory_profile = \"claude\"\ncontext_gate = \"off\"\n"
}

#[test]
fn migrate_legacy_claude_context_gate_dry_run_does_not_write() -> Result<()> {
    let path = migration_temp_config_path("runtime-claude-gate-migrate-dry-run");
    with_migration_config_path(&path, || -> Result<()> {
        std::fs::write(&path, legacy_claude_gate_config())?;

        let migration = migrate_legacy_claude_context_gate(true)?;
        let text = std::fs::read_to_string(&path)?;
        let host = resolve_host_runtime_config(Some("claude-code"))?;

        assert!(migration.changed);
        assert!(migration.dry_run);
        assert_eq!(migration.old_gate.as_deref(), Some("off"));
        assert_eq!(migration.new_gate.as_deref(), Some("auto"));
        assert_eq!(host.context_gate.as_deref(), Some("off"));
        assert!(text.contains("context_gate = \"off\""), "{text}");
        Ok(())
    })?;
    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn migrate_legacy_claude_context_gate_applies_explicit_user_command() -> Result<()> {
    let path = migration_temp_config_path("runtime-claude-gate-migrate-apply");
    with_migration_config_path(&path, || -> Result<()> {
        std::fs::write(&path, legacy_claude_gate_config())?;

        let migration = migrate_legacy_claude_context_gate(false)?;
        let text = std::fs::read_to_string(&path)?;
        let host = resolve_host_runtime_config(Some("claude-code"))?;

        assert!(migration.changed);
        assert!(!migration.dry_run);
        assert_eq!(migration.old_gate.as_deref(), Some("off"));
        assert_eq!(migration.new_gate.as_deref(), Some("auto"));
        assert_eq!(host.context_gate.as_deref(), Some("auto"));
        assert!(text.contains("context_gate = \"auto\""), "{text}");
        Ok(())
    })?;
    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn migrate_legacy_claude_context_gate_leaves_current_config_unchanged() -> Result<()> {
    let path = migration_temp_config_path("runtime-claude-gate-migrate-current");
    with_migration_config_path(&path, || -> Result<()> {
        init_config()?;

        let migration = migrate_legacy_claude_context_gate(false)?;
        let host = resolve_host_runtime_config(Some("claude-code"))?;

        assert!(!migration.changed);
        assert_eq!(migration.old_gate.as_deref(), Some("auto"));
        assert_eq!(migration.new_gate.as_deref(), Some("auto"));
        assert_eq!(host.context_gate.as_deref(), Some("auto"));
        Ok(())
    })?;
    std::fs::remove_file(path)?;
    Ok(())
}