remem-ai 0.6.17

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::Result;
use rusqlite::{params, Connection};

use super::MIGRATIONS;

#[test]
fn capture_git_evidence_migration_preserves_legacy_links() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA foreign_keys=ON;")?;
    for migration in &MIGRATIONS[..66] {
        conn.execute_batch(migration.sql)?;
    }
    conn.execute(
        "INSERT INTO git_commits
         (project, repo_path, sha, short_sha, branch, message, authored_at_epoch,
          changed_files, created_at_epoch, updated_at_epoch)
         VALUES ('proj', '/repo', ?1, 'abcdef1', 'main', 'legacy link', 10,
                 '[]', 10, 10)",
        ["abcdef1234567890abcdef1234567890abcdef12"],
    )?;
    conn.execute(
        "INSERT OR IGNORE INTO hosts(name, enabled, created_at_epoch) VALUES ('codex-cli', 1, 10)",
        [],
    )?;
    let host_id: i64 =
        conn.query_row("SELECT id FROM hosts WHERE name = 'codex-cli'", [], |row| {
            row.get(0)
        })?;
    conn.execute(
        "INSERT OR IGNORE INTO workspaces(root_path, created_at_epoch, updated_at_epoch)
         VALUES ('/repo', 10, 10)",
        [],
    )?;
    let workspace_id: i64 = conn.query_row(
        "SELECT id FROM workspaces WHERE root_path = '/repo'",
        [],
        |row| row.get(0),
    )?;
    conn.execute(
        "INSERT INTO projects(workspace_id, project_path, project_key, created_at_epoch, updated_at_epoch)
         VALUES (?1, '/repo', 'repo-key', 10, 10)",
        [workspace_id],
    )?;
    let project_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO sessions(host_id, workspace_id, project_id, session_id, last_seen_at_epoch, status)
         VALUES (?1, ?2, ?3, 'mapped-session', 10, 'active')",
        params![host_id, workspace_id, project_id],
    )?;
    let mapped_session_row_id = conn.last_insert_rowid();
    let mapped_sha = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
    conn.execute(
        "INSERT INTO git_commits
         (project, repo_path, sha, short_sha, branch, message, authored_at_epoch,
          changed_files, created_at_epoch, updated_at_epoch)
         VALUES ('/repo', '/repo', ?1, 'bbbbbbb', 'main', 'mapped link', 10,
                 '[]', 10, 10)",
        [mapped_sha],
    )?;
    let mapped_commit_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO git_commit_sessions
         (commit_id, session_id, memory_session_id, source, linked_at_epoch)
         VALUES (?1, 'mapped-session', ?2, 'git_metadata', 10)",
        params![
            mapped_commit_id,
            format!("capture-rollup-{mapped_session_row_id}")
        ],
    )?;
    let commit_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO git_commit_sessions
         (commit_id, session_id, memory_session_id, source, linked_at_epoch)
         VALUES (?1, 'legacy-session', 'legacy-memory', 'git_metadata', 10)",
        [commit_id],
    )?;

    let migration = &MIGRATIONS[66];
    assert_eq!(migration.version, 67);
    conn.execute_batch(migration.sql)?;

    let legacy: (i64, Option<i64>, String, String, String) = conn.query_row(
        "SELECT commit_id, session_row_id, session_id, memory_session_id, source
         FROM git_commit_sessions WHERE commit_id = ?1",
        [commit_id],
        |row| {
            Ok((
                row.get(0)?,
                row.get(1)?,
                row.get(2)?,
                row.get(3)?,
                row.get(4)?,
            ))
        },
    )?;
    assert_eq!(legacy.0, commit_id);
    assert_eq!(legacy.1, None);
    assert_eq!(legacy.2, "legacy-session");
    assert_eq!(legacy.3, "legacy-memory");
    assert_eq!(legacy.4, "git_metadata");
    let mapped: Option<i64> = conn.query_row(
        "SELECT session_row_id FROM git_commit_sessions WHERE commit_id = ?1",
        [mapped_commit_id],
        |row| row.get(0),
    )?;
    assert_eq!(mapped, Some(mapped_session_row_id));
    conn.prepare("SELECT event_row_id, sha, metadata_json, evidence_kind, evidence_locator FROM captured_event_commits LIMIT 0")?;
    let metadata = crate::git_util::GitCommitMetadata {
        repo_path: "/repo".to_string(),
        sha: mapped_sha.to_string(),
        short_sha: "bbbbbbb".to_string(),
        branch: Some("main".to_string()),
        message: Some("mapped link".to_string()),
        authored_at_epoch: Some(10),
        changed_files: Vec::new(),
    };
    crate::git_trace::link_captured_git_metadata_to_session(
        &conn,
        "/repo",
        mapped_session_row_id,
        "mapped-session",
        &format!("capture-rollup-{mapped_session_row_id}"),
        &metadata,
    )?;
    let mapped_count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM git_commit_sessions WHERE commit_id = ?1",
        [mapped_commit_id],
        |row| row.get(0),
    )?;
    assert_eq!(mapped_count, 1);
    let delete_error = conn
        .execute(
            "DELETE FROM sessions WHERE id = ?1",
            [mapped_session_row_id],
        )
        .expect_err("durable commit link should restrict session deletion");
    assert!(delete_error.to_string().contains("FOREIGN KEY"));
    let foreign_key_errors: i64 = conn.query_row(
        "SELECT COUNT(*) FROM pragma_foreign_key_check",
        params![],
        |row| row.get(0),
    )?;
    assert_eq!(foreign_key_errors, 0);
    Ok(())
}