chkpt-core 0.3.1

Core library for chkpt – a fast, content-addressable checkpoint system
Documentation
use chkpt_core::error::ChkpttError;
use chkpt_core::store::catalog::{BlobLocation, CatalogSnapshot, ManifestEntry, MetadataCatalog};
use chkpt_core::store::snapshot::SnapshotStats;
use chrono::{TimeZone, Utc};
use tempfile::TempDir;

fn sample_snapshot(
    id: &str,
    second: u32,
    message: Option<&str>,
    parent_snapshot_id: Option<&str>,
) -> CatalogSnapshot {
    CatalogSnapshot {
        id: id.to_string(),
        created_at: Utc.with_ymd_and_hms(2026, 3, 31, 1, 0, second).unwrap(),
        message: message.map(|value| value.to_string()),
        parent_snapshot_id: parent_snapshot_id.map(|value| value.to_string()),
        manifest_snapshot_id: None,
        root_tree_hash: Some([second as u8; 16]),
        stats: SnapshotStats {
            total_files: 2,
            total_bytes: 15,
            new_objects: 1,
        },
    }
}

fn sample_manifest() -> Vec<ManifestEntry> {
    vec![
        ManifestEntry {
            path: "a.txt".into(),
            blob_hash: [1u8; 16],
            size: 5,
            mode: 0o100644,
        },
        ManifestEntry {
            path: "nested/b.txt".into(),
            blob_hash: [2u8; 16],
            size: 10,
            mode: 0o100644,
        },
    ]
}

#[test]
fn test_catalog_snapshot_manifest_round_trip() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let snapshot = sample_snapshot("snap-a", 0, Some("first"), None);
    let manifest = sample_manifest();

    catalog.insert_snapshot(&snapshot, &manifest).unwrap();

    let loaded = catalog.load_snapshot("snap-a").unwrap();
    assert_eq!(loaded.id, snapshot.id);
    assert_eq!(loaded.message.as_deref(), Some("first"));
    assert_eq!(loaded.root_tree_hash, snapshot.root_tree_hash);
    assert_eq!(catalog.snapshot_manifest("snap-a").unwrap(), manifest);
}

#[test]
fn test_catalog_latest_and_prefix_resolution() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let older = sample_snapshot("019d417e-older", 0, Some("older"), None);
    let newer = sample_snapshot("019d417e-newer", 1, Some("newer"), Some("019d417e-older"));
    let manifest = sample_manifest();

    catalog.insert_snapshot(&older, &manifest).unwrap();
    catalog.insert_snapshot(&newer, &manifest).unwrap();

    assert_eq!(catalog.latest_snapshot().unwrap().unwrap().id, newer.id);
    assert_eq!(catalog.resolve_snapshot_ref("latest").unwrap().id, newer.id);
    assert_eq!(
        catalog.resolve_snapshot_ref("019d417e-new").unwrap().id,
        newer.id
    );
}

#[test]
fn test_catalog_rejects_ambiguous_prefix() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let manifest = sample_manifest();

    catalog
        .insert_snapshot(&sample_snapshot("019d417e-aa", 0, None, None), &manifest)
        .unwrap();
    catalog
        .insert_snapshot(&sample_snapshot("019d417e-ab", 1, None, None), &manifest)
        .unwrap();

    let err = catalog.resolve_snapshot_ref("019d417e-a").unwrap_err();
    assert!(
        matches!(err, ChkpttError::Other(message) if message.contains("Ambiguous snapshot prefix"))
    );
}

#[test]
fn test_catalog_tracks_blob_locations_and_cascades_manifest_rows() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let snapshot = sample_snapshot("snap-a", 0, Some("first"), None);
    let manifest = sample_manifest();

    catalog.insert_snapshot(&snapshot, &manifest).unwrap();
    catalog
        .bulk_upsert_blob_locations(&[(
            [9u8; 16],
            BlobLocation {
                pack_hash: Some("pack-1".into()),
                size: 42,
            },
        )])
        .unwrap();

    let location = catalog.blob_location(&[9u8; 16]).unwrap().unwrap();
    assert_eq!(location.pack_hash.as_deref(), Some("pack-1"));
    assert_eq!(location.size, 42);

    catalog.delete_snapshot("snap-a").unwrap();
    assert!(matches!(
        catalog.load_snapshot("snap-a"),
        Err(ChkpttError::SnapshotNotFound(_))
    ));
    assert!(catalog.snapshot_manifest("snap-a").unwrap().is_empty());
}

#[test]
fn test_catalog_reuses_manifest_for_metadata_only_snapshots() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let base = sample_snapshot("snap-a", 0, Some("first"), None);
    let alias = sample_snapshot("snap-b", 1, Some("second"), Some("snap-a"));
    let manifest = sample_manifest();

    catalog.insert_snapshot(&base, &manifest).unwrap();
    catalog
        .insert_snapshot_metadata_only(&alias, &base.id)
        .unwrap();

    assert_eq!(catalog.snapshot_manifest("snap-b").unwrap(), manifest);
}

#[test]
fn test_catalog_delete_transfers_manifest_ownership_to_alias() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();
    let base = sample_snapshot("snap-a", 0, Some("first"), None);
    let alias = sample_snapshot("snap-b", 1, Some("second"), Some("snap-a"));
    let manifest = sample_manifest();

    catalog.insert_snapshot(&base, &manifest).unwrap();
    catalog
        .insert_snapshot_metadata_only(&alias, &base.id)
        .unwrap();

    catalog.delete_snapshot("snap-a").unwrap();

    assert_eq!(catalog.snapshot_manifest("snap-b").unwrap(), manifest);
}

#[test]
fn test_catalog_bulk_upserts_and_lists_blob_hashes() {
    let dir = TempDir::new().unwrap();
    let catalog = MetadataCatalog::open(dir.path().join("catalog.sqlite")).unwrap();

    catalog
        .bulk_upsert_blob_locations(&[
            (
                [3u8; 16],
                BlobLocation {
                    pack_hash: Some("pack-a".into()),
                    size: 10,
                },
            ),
            (
                [4u8; 16],
                BlobLocation {
                    pack_hash: None,
                    size: 20,
                },
            ),
        ])
        .unwrap();

    let known = catalog.all_blob_hashes().unwrap();
    assert!(known.contains(&[3u8; 16]));
    assert!(known.contains(&[4u8; 16]));
}