remem-ai 0.5.145

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

use super::support::setup_workstream_schema;
use crate::workstream::{
    auto_abandon_all_inactive_at, auto_abandon_inactive, auto_pause_all_inactive_at,
    auto_pause_inactive, count_auto_abandon_all_inactive_at, count_auto_pause_all_inactive_at,
    query_workstreams, DEFAULT_AUTO_ABANDON_DAYS, DEFAULT_AUTO_PAUSE_DAYS,
};

#[test]
fn test_auto_pause_after_14_days() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - ((DEFAULT_AUTO_PAUSE_DAYS + 1) * 86400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Stale Task', 'active', ?1, ?1)",
        params![old_epoch],
    )
    .unwrap();

    let paused = auto_pause_inactive(&conn, "test/proj", DEFAULT_AUTO_PAUSE_DAYS).unwrap();
    assert_eq!(paused, 1);

    let workstreams = query_workstreams(&conn, "test/proj", Some("paused")).unwrap();
    assert_eq!(workstreams.len(), 1);
}

#[test]
fn test_auto_abandon_after_30_days() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - (31 * 86400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Very Stale', 'paused', ?1, ?1)",
        params![old_epoch],
    )
    .unwrap();

    let abandoned = auto_abandon_inactive(&conn, "test/proj", 30).unwrap();
    assert_eq!(abandoned, 1);

    let workstreams = query_workstreams(&conn, "test/proj", Some("abandoned")).unwrap();
    assert_eq!(workstreams.len(), 1);
}

#[test]
fn test_auto_pause_skips_recent() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let recent = chrono::Utc::now().timestamp() - ((DEFAULT_AUTO_PAUSE_DAYS - 1) * 86400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Recent Task', 'active', ?1, ?1)",
        params![recent],
    )
    .unwrap();

    let paused = auto_pause_inactive(&conn, "test/proj", DEFAULT_AUTO_PAUSE_DAYS).unwrap();
    assert_eq!(paused, 0);
}

#[test]
fn test_auto_pause_uses_repo_owner_metadata() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - ((DEFAULT_AUTO_PAUSE_DAYS + 1) * 86400);
    conn.execute(
        "INSERT INTO workstreams
         (project, title, status, created_at_epoch, updated_at_epoch,
          owner_scope, owner_key, target_project)
         VALUES ('legacy/path', 'Owned Task', 'active', ?1, ?1,
                 'repo', 'test/proj', 'test/proj')",
        params![old_epoch],
    )
    .unwrap();

    let paused = auto_pause_inactive(&conn, "test/proj", DEFAULT_AUTO_PAUSE_DAYS).unwrap();
    assert_eq!(paused, 1);
}

#[test]
fn test_auto_pause_uses_workstream_owner_target_project() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - ((DEFAULT_AUTO_PAUSE_DAYS + 1) * 86400);
    conn.execute(
        "INSERT INTO workstreams
         (project, title, status, created_at_epoch, updated_at_epoch,
          owner_scope, owner_key, target_project)
         VALUES ('legacy/path', 'Owned Workstream', 'active', ?1, ?1,
                 'workstream', 'ws-123', 'test/proj')",
        params![old_epoch],
    )
    .unwrap();

    let paused = auto_pause_inactive(&conn, "test/proj", DEFAULT_AUTO_PAUSE_DAYS).unwrap();
    assert_eq!(paused, 1);
}

#[test]
fn test_cleanup_sequence_does_not_abandon_newly_paused_workstream() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - (45 * 86400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Very Old Active', 'active', ?1, ?1)",
        params![old_epoch],
    )
    .unwrap();

    let paused = auto_pause_inactive(&conn, "test/proj", DEFAULT_AUTO_PAUSE_DAYS).unwrap();
    assert_eq!(paused, 1);
    let abandoned = auto_abandon_inactive(&conn, "test/proj", 30).unwrap();
    assert_eq!(abandoned, 0);

    let workstreams = query_workstreams(&conn, "test/proj", Some("paused")).unwrap();
    assert_eq!(workstreams.len(), 1);
}

#[test]
fn all_project_cleanup_counts_match_workstream_apply_sequence() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let now = 2_000_000_000;
    let very_old = now - (45 * 86_400);
    let old_paused = now - ((DEFAULT_AUTO_ABANDON_DAYS + 1) * 86_400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Very Old Active', 'active', ?1, ?1)",
        params![very_old],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Old Paused', 'paused', ?1, ?1)",
        params![old_paused],
    )
    .unwrap();
    conn.execute(
        "INSERT INTO workstreams
         (project, title, status, created_at_epoch, updated_at_epoch, merged_into_workstream_id)
         VALUES ('test/proj', 'Merged Old Active', 'active', ?1, ?1, 1)",
        params![very_old],
    )
    .unwrap();
    let merged_active_id = conn.last_insert_rowid();
    conn.execute(
        "INSERT INTO workstreams
         (project, title, status, created_at_epoch, updated_at_epoch, merged_into_workstream_id)
         VALUES ('test/proj', 'Merged Old Paused', 'paused', ?1, ?1, 1)",
        params![old_paused],
    )
    .unwrap();
    let merged_paused_id = conn.last_insert_rowid();

    assert_eq!(
        count_auto_pause_all_inactive_at(&conn, now, DEFAULT_AUTO_PAUSE_DAYS).unwrap(),
        1
    );
    assert_eq!(
        count_auto_abandon_all_inactive_at(&conn, now, DEFAULT_AUTO_ABANDON_DAYS).unwrap(),
        1
    );
    assert_eq!(
        auto_pause_all_inactive_at(&conn, now, DEFAULT_AUTO_PAUSE_DAYS).unwrap(),
        1
    );
    assert_eq!(
        auto_abandon_all_inactive_at(&conn, now, DEFAULT_AUTO_ABANDON_DAYS).unwrap(),
        1
    );

    let paused = query_workstreams(&conn, "test/proj", Some("paused")).unwrap();
    assert_eq!(paused.len(), 1);
    assert_eq!(paused[0].title, "Very Old Active");
    let abandoned = query_workstreams(&conn, "test/proj", Some("abandoned")).unwrap();
    assert_eq!(abandoned.len(), 1);
    assert_eq!(abandoned[0].title, "Old Paused");

    let merged_statuses: Vec<String> = [merged_active_id, merged_paused_id]
        .into_iter()
        .map(|id| {
            conn.query_row(
                "SELECT status FROM workstreams WHERE id = ?1",
                params![id],
                |row| row.get(0),
            )
            .unwrap()
        })
        .collect();
    assert_eq!(merged_statuses, vec!["active", "paused"]);
}

#[test]
fn test_auto_abandon_skips_active() {
    let conn = Connection::open_in_memory().unwrap();
    setup_workstream_schema(&conn);

    let old_epoch = chrono::Utc::now().timestamp() - (31 * 86400);
    conn.execute(
        "INSERT INTO workstreams (project, title, status, created_at_epoch, updated_at_epoch)
         VALUES ('test/proj', 'Old Active', 'active', ?1, ?1)",
        params![old_epoch],
    )
    .unwrap();

    let abandoned = auto_abandon_inactive(&conn, "test/proj", 30).unwrap();
    assert_eq!(abandoned, 0);
}