Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle-git-metadata src commit parse test success.rs
use crate::commit::parse::parse;

#[test]
fn root_commit() {
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000

Initial commit"#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(commit.tree(), "abc123");
    assert_eq!(commit.parents().count(), 0);
    assert_eq!(commit.author().name, "Alice Liddell");
    assert_eq!(commit.author().email, "alice@example.com");
    assert_eq!(commit.author().time.seconds(), 1700000000);
    assert_eq!(commit.author().time.offset(), 0);
    assert_eq!(commit.committer().name, "Alice Liddell");
    assert_eq!(commit.message(), "Initial commit");
    assert_eq!(commit.trailers().count(), 0);
    assert_eq!(commit.headers().count(), 0);
}

#[test]
fn commit_with_single_parent() {
    let raw = r#"tree def456
parent abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000

Second commit"#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(commit.tree(), "def456");
    assert_eq!(commit.parents().collect::<Vec<_>>(), ["abc123"]);
    assert_eq!(commit.message(), "Second commit");
}

#[test]
fn merge_commit() {
    let raw = r#"tree ghi789
parent abc123
parent def456
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000

Merge branch 'feature'"#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(commit.parents().collect::<Vec<_>>(), ["abc123", "def456"]);
}

#[test]
fn commit_with_multiline_gpgsig() {
    // gpgsig continuation lines are indented by one space in the raw object.
    // The parser folds them back into the header value with embedded newlines.
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000
gpgsig -----BEGIN SSH SIGNATURE-----
 AAAAB3NzaC1yc2EAAAADAQAB
 AAAA==
 -----END SSH SIGNATURE-----

Signed commit"#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(commit.signatures().count(), 1);
    // gpgsig is stored in headers; it is the only extra header here.
    assert_eq!(commit.headers().count(), 1);
    assert_eq!(commit.message(), "Signed commit");
}

#[test]
fn commit_gpgsig_is_preserved_and_strip_removes_it() {
    // Parsing preserves gpgsig so callers can extract it before stripping.
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000
gpgsig -----BEGIN SSH SIGNATURE-----
 AAAA==
 -----END SSH SIGNATURE-----

Signed commit"#;

    let commit = parse::<String, String>(raw).unwrap();
    assert_eq!(commit.signatures().count(), 1);

    let stripped = commit.strip_signatures();
    assert_eq!(stripped.signatures().count(), 0);
    assert_eq!(stripped.headers().count(), 0);
    assert_eq!(stripped.message(), "Signed commit");
}

#[test]
fn commit_with_trailers() {
    // The last paragraph contains only valid Token: value lines, so they
    // are split out into the trailers vec and excluded from the message.
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Bob Bobson <bob@example.com> 1700000001 +0100

Add a new feature

This commit adds a new feature to the library.

Signed-off-by: Alice Liddell <alice@example.com>
Co-authored-by: Bob Bobson <bob@example.com>"#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(
        commit.message(),
        "Add a new feature\n\nThis commit adds a new feature to the library."
    );
    let trailers: Vec<_> = commit.trailers().collect();
    assert_eq!(trailers.len(), 2);
    assert_eq!(&*trailers[0].token, "Signed-off-by");
    assert_eq!(trailers[0].value, "Alice Liddell <alice@example.com>");
    assert_eq!(&*trailers[1].token, "Co-authored-by");
    assert_eq!(trailers[1].value, "Bob Bobson <bob@example.com>");
}

#[test]
fn commit_last_paragraph_kept_in_message_when_not_all_trailers() {
    // If any line in the last paragraph is not a valid Token: value pair,
    // the entire paragraph stays in the message and no trailers are extracted.
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000

Add feature

Signed-off-by: Alice Liddell <alice@example.com>
This line is not a valid trailer."#;

    let commit = parse::<String, String>(raw).unwrap();

    assert_eq!(commit.trailers().count(), 0);
    assert!(commit.message().contains("Signed-off-by"));
    assert!(
        commit
            .message()
            .contains("This line is not a valid trailer.")
    );
}

#[test]
fn commit_with_extra_headers() {
    let raw = r#"tree abc123
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Alice Liddell <alice@example.com> 1700000000 +0000
encoding UTF-8
mergetag some-value

Commit with extra headers"#;

    let commit = parse::<String, String>(raw).unwrap();

    let headers: Vec<_> = commit.headers().collect();
    assert_eq!(headers.len(), 2);
    assert_eq!(headers[0], ("encoding", "UTF-8"));
    assert_eq!(headers[1], ("mergetag", "some-value"));
}

#[test]
fn roundtrip() {
    // Parsing and then re-displaying a commit must produce output that parses
    // back to a CommitData equal in every field, exercising the Display /
    // parse_body symmetry in particular.
    let raw = r#"tree abc123
parent def456
author Alice Liddell <alice@example.com> 1700000000 +0000
committer Bob Bobson <bob@example.com> 1700000001 +0100

Add something useful

Signed-off-by: Alice Liddell <alice@example.com>"#;

    let commit = parse::<String, String>(raw).unwrap();
    let displayed = commit.to_string();
    let reparsed = parse::<String, String>(&displayed).unwrap();

    assert_eq!(commit.tree(), reparsed.tree());
    assert_eq!(
        commit.parents().collect::<Vec<_>>(),
        reparsed.parents().collect::<Vec<_>>()
    );
    assert_eq!(commit.author(), reparsed.author());
    assert_eq!(commit.committer(), reparsed.committer());
    assert_eq!(commit.message(), reparsed.message());
    assert_eq!(
        commit.trailers().collect::<Vec<_>>(),
        reparsed.trailers().collect::<Vec<_>>()
    );
}