ic-backup 0.7.0

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
use super::*;

#[test]
fn digest_ingress_preserves_equivalent_case_exact_records_and_original_errors() {
    let hash = "0123456789abcdef".repeat(4);
    let record = ArtifactChecksumRecord::from_hash(&hash).unwrap();
    for input in [hash.clone(), hash.to_ascii_uppercase(), "aA0F".repeat(16)] {
        let admitted = ArtifactChecksumRecord::from_hash(&input).unwrap();
        assert_eq!(admitted.hash(), input.to_ascii_lowercase());
        admitted.verify(&input).unwrap();
        let decoded: ArtifactChecksumRecord =
            serde_json::from_value(serde_json::json!({"algorithm":"sha256", "hash":input}))
                .unwrap();
        assert_eq!(decoded, admitted);
        assert_eq!(
            serde_json::to_value(admitted).unwrap(),
            serde_json::json!({"algorithm":"sha256", "hash":input.to_ascii_lowercase()})
        );
    }
    for invalid in [
        String::new(),
        "A".repeat(63),
        "A".repeat(65),
        "G".repeat(64),
        "\0".repeat(64),
        "é".repeat(32),
        format!("{hash} "),
    ] {
        assert!(
            matches!(ArtifactChecksumRecord::from_hash(&invalid), Err(ChecksumError::InvalidHash(original)) if original == invalid)
        );
        assert!(
            matches!(record.verify(&invalid), Err(ChecksumError::InvalidHash(original)) if original == invalid)
        );
    }
    assert!(
        matches!(record.verify(&"AB".repeat(32)), Err(ChecksumError::ChecksumMismatch { expected, actual }) if expected == "ab".repeat(32) && actual == hash)
    );
    assert_eq!(record.hash(), hash);
}

#[test]
fn raw_digest_projection_preserves_leading_zero_bytes_and_exact_record_fields() {
    let bytes: [u8; 32] = std::array::from_fn(|index| u8::try_from(index).unwrap());
    let record = ArtifactChecksumRecord::from_digest(bytes);
    let hash = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
    assert_eq!(record.hash(), hash);
    assert_eq!(
        serde_json::to_value(&record).unwrap(),
        serde_json::json!({"algorithm":"sha256", "hash":hash})
    );
    assert_eq!(record, ArtifactChecksumRecord::from_hash(hash).unwrap());
}

#[test]
fn checksum_record_normalizes_equivalent_hashes_at_decode() {
    let record = ArtifactChecksumRecord::from_bytes(b"snapshot");
    let encoded =
        serde_json::json!({"algorithm": "sha256", "hash": record.hash().to_ascii_uppercase()});
    let decoded: ArtifactChecksumRecord =
        serde_json::from_value(encoded).expect("decode uppercase digest");
    assert_eq!(decoded, record);
    assert_eq!(
        serde_json::to_value(decoded).expect("encode record"),
        serde_json::json!({"algorithm": "sha256", "hash": record.hash()})
    );
}

#[test]
fn checksum_record_requires_exact_valid_fields() {
    let digest = ArtifactChecksumRecord::from_bytes(b"snapshot");
    for value in [
        serde_json::json!({"hash": digest.hash()}),
        serde_json::json!({"algorithm": "sha256"}),
        serde_json::json!({"algorithm": "sha512", "hash": digest.hash()}),
        serde_json::json!({"algorithm": "sha256", "hash": "invalid"}),
        serde_json::json!({"algorithm": "sha256", "hash": digest.hash(), "extra": true}),
    ] {
        assert!(serde_json::from_value::<ArtifactChecksumRecord>(value).is_err());
    }
    assert!(matches!(
        ArtifactChecksumRecord::from_hash("invalid"),
        Err(ChecksumError::InvalidHash(_))
    ));
}