frigg 0.4.2

Local-first MCP server for code understanding.
Documentation
use super::support::*;

#[test]
fn snapshot_and_provenance_pruning_keep_storage_bounded() -> FriggResult<()> {
    let db_path = temp_db_path("storage-prune-bounded");
    let storage = Storage::new(&db_path);
    storage.initialize()?;

    for idx in 1..=4 {
        storage.upsert_manifest(
            "repo-1",
            &format!("snapshot-00{idx}"),
            &[manifest_entry(
                "src/lib.rs",
                &format!("hash-{idx}"),
                10 + idx as u64,
                Some(100 + idx as u64),
            )],
        )?;
    }
    replace_semantic_records(
        &storage,
        "repo-1",
        "snapshot-002",
        &[semantic_record(
            "chunk-live",
            "repo-1",
            "snapshot-002",
            "src/lib.rs",
            "rust",
            0,
            1,
            10,
            "openai",
            "text-embedding-3-small",
            Some("trace-live"),
            "hash-live",
            "fn live() {}",
            &[0.1, 0.2],
        )],
    )?;

    for idx in 0..5 {
        storage.append_provenance_event(
            &format!("trace-{idx}"),
            "read_file",
            &json!({ "idx": idx }),
        )?;
    }
    let pruned_provenance = storage.prune_provenance_events(2)?;
    assert_eq!(pruned_provenance, 3);
    assert_eq!(storage.load_recent_provenance_events(10)?.len(), 2);

    let deleted_snapshots = storage.prune_repository_snapshots("repo-1", 1)?;
    assert_eq!(deleted_snapshots, 2);
    assert!(
        storage.load_manifest_for_snapshot("snapshot-002")?.len() == 1,
        "semantic-head snapshot should be protected from pruning"
    );
    assert!(
        storage.load_manifest_for_snapshot("snapshot-004")?.len() == 1,
        "latest retained snapshot should remain available"
    );
    assert!(
        storage
            .load_manifest_for_snapshot("snapshot-001")?
            .is_empty(),
        "oldest pruned snapshot should be removed"
    );
    assert!(
        storage
            .load_manifest_for_snapshot("snapshot-003")?
            .is_empty(),
        "non-protected stale snapshot should be removed"
    );

    cleanup_db(&db_path);
    Ok(())
}

#[test]
fn snapshot_prune_keeps_non_manifest_rows_out_of_manifest_retention() -> FriggResult<()> {
    let db_path = temp_db_path("storage-prune-manifest-vs-non-manifest");
    let storage = Storage::new(&db_path);
    storage.initialize()?;

    storage.upsert_manifest(
        "repo-1",
        "snapshot-manifest-old",
        &[manifest_entry(
            "src/lib.rs",
            "hash-manifest-old",
            101,
            Some(1001),
        )],
    )?;
    storage.upsert_manifest(
        "repo-1",
        "snapshot-manifest-new",
        &[manifest_entry(
            "src/lib.rs",
            "hash-manifest-new",
            102,
            Some(1002),
        )],
    )?;

    let conn = open_test_connection(&db_path)?;
    conn.execute(
        "INSERT INTO snapshot (snapshot_id, repository_id, kind, revision, created_at) VALUES (?1, ?2, 'semantic', NULL, ?3)",
        ("snapshot-semantic-only", "repo-1", "2026-01-03T00:00:00Z"),
    )
    .map_err(|err| {
        FriggError::Internal(format!(
            "failed to seed non-manifest snapshot for retention test: {err}"
        ))
    })?;

    let deleted = storage.prune_repository_snapshots("repo-1", 1)?;
    assert_eq!(deleted, 1);

    let manifest_count: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM snapshot WHERE repository_id = ?1 AND kind = 'manifest'",
            ["repo-1"],
            |row| row.get(0),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to count manifest snapshots after prune in test: {err}"
            ))
        })?;
    let non_manifest_count: i64 = conn
        .query_row(
            "SELECT COUNT(*) FROM snapshot WHERE repository_id = ?1 AND kind = 'semantic'",
            ["repo-1"],
            |row| row.get(0),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to count non-manifest snapshots after prune in test: {err}"
            ))
        })?;

    assert_eq!(manifest_count, 1);
    assert_eq!(non_manifest_count, 1);
    assert!(
        storage
            .load_manifest_for_snapshot("snapshot-manifest-old")?
            .is_empty(),
        "oldest manifest snapshot should be pruned by manifest retention"
    );
    assert!(
        storage
            .load_manifest_for_snapshot("snapshot-manifest-new")?
            .len()
            == 1,
        "latest manifest snapshot should remain after manifest retention"
    );
    assert_eq!(
        conn.query_row(
            "SELECT COUNT(*) FROM snapshot WHERE snapshot_id = ?1",
            ["snapshot-semantic-only"],
            |row| row.get::<_, i64>(0),
        )
        .map_err(|err| {
            FriggError::Internal(format!(
                "failed to assert non-manifest snapshot retained during manifest prune: {err}"
            ))
        })?,
        1
    );

    cleanup_db(&db_path);
    Ok(())
}