remem-ai 0.6.35

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

use super::run_migrations;

#[test]
fn user_context_summary_migration_creates_rows_and_indexes() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    for name in [
        "user_context_claims",
        "user_context_summaries",
        "idx_user_context_summaries_owner_active",
        "idx_user_context_summaries_user_recent",
    ] {
        let exists: bool = conn.query_row(
            "SELECT 1 FROM sqlite_master WHERE name = ?1",
            [name],
            |_| Ok(true),
        )?;
        assert!(exists, "{name} should exist after user-context migrations");
    }
    Ok(())
}

#[test]
fn user_context_summary_source_json_is_schema_validated() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    let err = insert_summary_row(&conn, "not-json", "[]", "[]")
        .expect_err("invalid source JSON should fail schema checks");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_summary_row(&conn, "{}", "[]", "[]")
        .expect_err("source claim JSON must be an array");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_summary_row(&conn, "[]", "{}", "[]")
        .expect_err("source memory JSON must be an array");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_summary_row(&conn, "[]", "[]", "{}")
        .expect_err("source activity JSON must be an array");
    assert!(err.to_string().contains("CHECK"));
    Ok(())
}

#[test]
fn memory_suppression_and_feedback_migration_creates_rows_and_indexes() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    for name in [
        "memory_suppressions",
        "memory_feedback",
        "idx_memory_suppressions_target_active",
        "idx_memory_suppressions_owner_active",
        "idx_memory_feedback_target_recent",
        "idx_memory_feedback_context_item",
    ] {
        let exists: bool = conn.query_row(
            "SELECT 1 FROM sqlite_master WHERE name = ?1",
            [name],
            |_| Ok(true),
        )?;
        assert!(exists, "{name} should exist after suppression migration");
    }

    conn.execute(
        "INSERT INTO memory_suppressions
         (target_kind, target_id, reason, actor, status, created_at_epoch, updated_at_epoch)
         VALUES ('memory', 1, 'not useful', 'cli', 'active', 10, 10)",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_feedback
         (target_kind, target_id, feedback, source, created_at_epoch)
         VALUES ('memory', 1, 'not_relevant', 'cli', 10)",
        [],
    )?;
    Ok(())
}

#[test]
fn memory_suppression_and_feedback_targets_are_schema_validated() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    let err = conn
        .execute(
            "INSERT INTO memory_suppressions
             (target_kind, reason, actor, status, created_at_epoch, updated_at_epoch)
             VALUES ('memory', 'missing target', 'cli', 'active', 10, 10)",
            [],
        )
        .expect_err("suppression target id/value should be required");
    assert!(err.to_string().contains("CHECK"));

    let err = conn
        .execute(
            "INSERT INTO memory_feedback
             (target_kind, target_value, feedback, source, created_at_epoch)
             VALUES ('topic_key', 'abc', 'not-relevant', 'cli', 10)",
            [],
        )
        .expect_err("feedback values should use stable underscore ids");
    assert!(err.to_string().contains("CHECK"));
    Ok(())
}

#[test]
fn user_context_candidate_migration_creates_rows_and_indexes() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    for name in [
        "user_context_candidates",
        "idx_user_context_candidates_inbox",
        "idx_user_context_candidates_user_recent",
        "idx_user_context_candidates_dedupe",
    ] {
        let exists: bool = conn.query_row(
            "SELECT 1 FROM sqlite_master WHERE name = ?1",
            [name],
            |_| Ok(true),
        )?;
        assert!(exists, "{name} should exist after candidate migration");
    }
    insert_candidate_row(
        &conn,
        r#"[{"kind":"session_summary","id":1}]"#,
        "pending_review",
        "low",
        "normal",
    )?;
    Ok(())
}

#[test]
fn user_context_candidate_schema_validates_sources_status_and_risk() -> Result<()> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
    run_migrations(&conn)?;

    let err = insert_candidate_row(&conn, "not-json", "pending_review", "low", "normal")
        .expect_err("source refs must be valid JSON");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_candidate_row(&conn, "{}", "pending_review", "low", "normal")
        .expect_err("source refs must be an array");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_candidate_row(&conn, "[]", "pending_review", "low", "normal")
        .expect_err("source refs must be non-empty");
    assert!(err.to_string().contains("CHECK"));
    let refs = r#"[{"kind":"session_summary","id":1}]"#;
    let err = insert_candidate_row(&conn, refs, "queued", "low", "normal")
        .expect_err("review status should be constrained");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_candidate_row(&conn, refs, "pending_review", "unsafe", "normal")
        .expect_err("risk class should be constrained");
    assert!(err.to_string().contains("CHECK"));
    let err = insert_candidate_row(&conn, refs, "pending_review", "low", "private")
        .expect_err("sensitivity should be constrained");
    assert!(err.to_string().contains("CHECK"));
    Ok(())
}

fn insert_summary_row(
    conn: &Connection,
    source_claim_ids_json: &str,
    source_memory_ids_json: &str,
    source_activity_refs_json: &str,
) -> rusqlite::Result<usize> {
    conn.execute(
        "INSERT INTO user_context_summaries
         (user_key, owner_scope, owner_key, scope, scope_key, summary_text,
          source_claim_ids_json, source_memory_ids_json, source_activity_refs_json,
          status, model, version, created_at_epoch, updated_at_epoch)
         VALUES ('user:default', 'user', 'user:default', 'project', '/repo',
                 'bad sources', ?1, ?2, ?3, 'active', 'test', 1, 10, 10)",
        params![
            source_claim_ids_json,
            source_memory_ids_json,
            source_activity_refs_json
        ],
    )
}

fn insert_candidate_row(
    conn: &Connection,
    source_refs_json: &str,
    review_status: &str,
    risk_class: &str,
    sensitivity: &str,
) -> rusqlite::Result<usize> {
    conn.execute(
        "INSERT INTO user_context_candidates
         (user_key, owner_scope, owner_key, source_project, host, session_id,
          claim_type, claim_key, claim_text, confidence, sensitivity, risk_class,
          source_kind, source_refs_json, source_preview, review_status,
          auto_promote_block_reason, review_note, created_at_epoch, updated_at_epoch)
         VALUES ('user:default', 'user', 'user:default', '/repo', 'codex-cli', 's1',
                 'preference', 'preference:test', 'Prefer review-gated user context.',
                 0.9, ?4, ?3, 'session_summary', ?1, 'source preview', ?2,
                 'requires_review', NULL, 10, 10)",
        params![source_refs_json, review_status, risk_class, sensitivity],
    )
}