remem-ai 0.6.93

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

fn migrated_db() -> Result<Connection> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    crate::migrate::run_migrations(&conn)?;
    Ok(conn)
}

fn metadata_input<'a>(changed_files: &'a [String]) -> CommitMetadataInput<'a> {
    CommitMetadataInput {
        project: "proj",
        repo_path: Some("/repo"),
        sha: "abcdef1234567890abcdef1234567890abcdef12",
        short_sha: Some("abcdef1"),
        branch: Some("main"),
        message: Some("Add traceability"),
        authored_at_epoch: Some(1_700_000_000),
        changed_files,
    }
}

#[test]
fn link_lookup_and_session_reverse_lookup_are_idempotent() -> Result<()> {
    let conn = migrated_db()?;
    let changed_files = vec!["src/lib.rs".to_string(), "README.md".to_string()];
    let input = CommitLinkInput {
        metadata: metadata_input(&changed_files),
        session_id: "content-session-1",
        memory_session_id: Some("mem-session-1"),
        source: "git_metadata",
    };

    let first_id = link_commit_to_session(&conn, &input)?;
    let second_id = link_commit_to_session(&conn, &input)?;
    assert_eq!(first_id, second_id);

    let link_count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM git_commit_sessions WHERE commit_id = ?1",
        [first_id],
        |row| row.get(0),
    )?;
    assert_eq!(link_count, 1);

    let full = lookup_commit(
        &conn,
        Some("proj"),
        "abcdef1234567890abcdef1234567890abcdef12",
    )?;
    assert_eq!(full.len(), 1);
    assert_eq!(full[0].git.short_sha, "abcdef1");
    assert_eq!(full[0].git.changed_files, changed_files);
    assert_eq!(full[0].sessions.len(), 1);
    assert_eq!(full[0].sessions[0].session_id, "content-session-1");
    let public_json = serde_json::to_value(&full[0])?;
    assert!(public_json["sessions"][0].get("session_row_id").is_none());

    let short = lookup_commit(&conn, Some("proj"), "abcdef1")?;
    assert_eq!(short.len(), 1);
    assert_eq!(short[0].git.sha, full[0].git.sha);

    let session_commits = commits_for_session(&conn, Some("proj"), "content-session-1", 10)?;
    assert_eq!(session_commits.len(), 1);
    assert_eq!(session_commits[0].git.short_sha, "abcdef1");

    let memory_session_commits = commits_for_session(&conn, Some("proj"), "mem-session-1", 10)?;
    assert_eq!(memory_session_commits.len(), 1);
    assert_eq!(
        memory_session_commits[0].link.memory_session_id.as_deref(),
        Some("mem-session-1")
    );

    link_observed_commit_to_session(
        &conn,
        "proj",
        "content-session-2",
        "mem-session-2",
        "abcdef1",
        Some("main"),
        None,
    )?;
    let commit_count: i64 =
        conn.query_row("SELECT COUNT(*) FROM git_commits", [], |row| row.get(0))?;
    assert_eq!(commit_count, 1);
    let relinked = lookup_commit(&conn, Some("proj"), "abcdef1")?;
    assert_eq!(relinked[0].sessions.len(), 2);
    Ok(())
}

#[test]
fn full_metadata_upgrades_existing_short_sha_record() -> Result<()> {
    let conn = migrated_db()?;
    link_observed_commit_to_session(
        &conn,
        "proj",
        "content-session-1",
        "mem-session-1",
        "abcdef1",
        Some("main"),
        None,
    )?;

    let changed_files = vec!["src/git_trace.rs".to_string()];
    link_commit_to_session(
        &conn,
        &CommitLinkInput {
            metadata: metadata_input(&changed_files),
            session_id: "content-session-2",
            memory_session_id: Some("mem-session-2"),
            source: "git_metadata",
        },
    )?;

    let commit_count: i64 =
        conn.query_row("SELECT COUNT(*) FROM git_commits", [], |row| row.get(0))?;
    assert_eq!(commit_count, 1);
    let results = lookup_commit(
        &conn,
        Some("proj"),
        "abcdef1234567890abcdef1234567890abcdef12",
    )?;
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].git.short_sha, "abcdef1");
    assert_eq!(results[0].git.changed_files, changed_files);
    assert_eq!(results[0].sessions.len(), 2);
    Ok(())
}

#[test]
fn full_metadata_upgrades_existing_long_abbrev_record() -> Result<()> {
    let conn = migrated_db()?;
    link_observed_commit_to_session(
        &conn,
        "proj",
        "content-session-1",
        "mem-session-1",
        "abcdef123456",
        Some("main"),
        None,
    )?;

    let changed_files = vec!["src/git_trace.rs".to_string()];
    link_commit_to_session(
        &conn,
        &CommitLinkInput {
            metadata: metadata_input(&changed_files),
            session_id: "content-session-2",
            memory_session_id: Some("mem-session-2"),
            source: "git_metadata",
        },
    )?;

    let commit_count: i64 =
        conn.query_row("SELECT COUNT(*) FROM git_commits", [], |row| row.get(0))?;
    assert_eq!(commit_count, 1);
    let results = lookup_commit(
        &conn,
        Some("proj"),
        "abcdef1234567890abcdef1234567890abcdef12",
    )?;
    assert_eq!(results.len(), 1);
    assert_eq!(
        results[0].git.sha,
        "abcdef1234567890abcdef1234567890abcdef12"
    );
    assert_eq!(results[0].git.short_sha, "abcdef1");
    assert_eq!(results[0].sessions.len(), 2);
    Ok(())
}

#[test]
fn relink_preserves_git_metadata_source() -> Result<()> {
    let conn = migrated_db()?;
    let changed_files = vec!["src/git_trace.rs".to_string()];
    link_observed_commit_to_session(
        &conn,
        "proj",
        "content-session-1",
        "mem-session-1",
        "abcdef1",
        Some("main"),
        None,
    )?;
    link_commit_to_session(
        &conn,
        &CommitLinkInput {
            metadata: metadata_input(&changed_files),
            session_id: "content-session-1",
            memory_session_id: Some("mem-session-1"),
            source: "git_metadata",
        },
    )?;
    link_observed_commit_to_session(
        &conn,
        "proj",
        "content-session-1",
        "mem-session-1",
        "abcdef1",
        Some("main"),
        None,
    )?;

    let source: String = conn.query_row(
        "SELECT source
         FROM git_commit_sessions
         WHERE session_id = 'content-session-1'",
        [],
        |row| row.get(0),
    )?;
    assert_eq!(source, "git_metadata");
    Ok(())
}

#[test]
fn commit_metadata_without_link_returns_no_sessions() -> Result<()> {
    let conn = migrated_db()?;
    let changed_files = Vec::new();
    let commit_id = upsert_commit_metadata(&conn, &metadata_input(&changed_files))?;
    assert!(commit_id > 0);

    let results = lookup_commit(&conn, Some("proj"), "abcdef1")?;
    assert_eq!(results.len(), 1);
    assert!(results[0].sessions.is_empty());
    Ok(())
}

#[test]
fn summary_is_memory_derived_and_optional() -> Result<()> {
    let conn = migrated_db()?;
    let changed_files = Vec::new();
    conn.execute(
        "INSERT INTO session_summaries
         (memory_session_id, project, request, completed, created_at, created_at_epoch)
         VALUES ('mem-session-1', 'proj', 'Need traceability', 'Linked commits',
                 '2026-01-01T00:00:00Z', 1)",
        [],
    )?;
    link_commit_to_session(
        &conn,
        &CommitLinkInput {
            metadata: metadata_input(&changed_files),
            session_id: "content-session-1",
            memory_session_id: Some("mem-session-1"),
            source: "git_metadata",
        },
    )?;

    let results = lookup_commit(&conn, Some("proj"), "abcdef1")?;
    let summary = results[0].sessions[0]
        .summary
        .as_ref()
        .expect("linked summary should be returned");
    assert_eq!(summary.request.as_deref(), Some("Need traceability"));
    assert_eq!(summary.completed.as_deref(), Some("Linked commits"));
    Ok(())
}