use bstr::ByteSlice;use gix_object::commit::{MessageRef, message::body::TrailerRef}; #[test]fn only_title_no_trailing_newline() { let msg = MessageRef::from_bytes(b"hello there"); assert_eq!( msg, MessageRef { title: b"hello there".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there");} #[test]fn title_and_body() { let msg = MessageRef::from_bytes(b"hello\n\nthere"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: Some("there".into()) } ); assert_eq!(msg.summary().as_ref(), "hello");} #[test]fn title_and_body_inconsistent_newlines() { let msg = MessageRef::from_bytes(b"hello\n\r\nthere"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: Some("there".into()) } ); assert_eq!(msg.summary().as_ref(), "hello");} #[test]fn only_title_trailing_newline_is_retained() { let msg = MessageRef::from_bytes(b"hello there\n"); assert_eq!( msg, MessageRef { title: b"hello there\n".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there");} #[test]fn only_title_trailing_windows_newline_is_retained() { let msg = MessageRef::from_bytes(b"hello there\r\n"); assert_eq!( msg, MessageRef { title: b"hello there\r\n".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there");} #[test]fn title_with_whitespace_and_body() { let msg = MessageRef::from_bytes(b"hello \t \r\n there\nanother line\n\nthe body\n\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \t \r\n there\nanother line".as_bstr(), body: Some(b"the body\n\n".as_bstr()) } );} #[test]fn title_with_more_whitespace_and_body() { let msg = MessageRef::from_bytes(b"hello \r\r\r\n there\nanother line\n\nthe body\n\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \r\r\r\n there\nanother line".as_bstr(), body: Some(b"the body\n\n".as_bstr()) } );} #[test]fn title_with_whitespace_and_body_windows_lineending() { let msg = MessageRef::from_bytes(b"hello \r\n \r\n there\nanother line\r\n\r\nthe body\n\r\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \r\n \r\n there\nanother line".as_bstr(), body: Some(b"the body\n\r\n".as_bstr()) } );} #[test]fn title_with_separator_and_empty_body() { let msg = MessageRef::from_bytes(b"hello\n\n"); assert_eq!(msg.summary().as_ref(), "hello"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: None } );} #[test]fn title_with_windows_separator_and_empty_body() { let msg = MessageRef::from_bytes(b"hello\r\n\r\n"); assert_eq!(msg.summary().as_ref(), "hello"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: None } );} /// Via `MessageRef`: a subject-only message with a trailer immediately/// after the blank line (the common case in the wild) must surface the/// trailer through the public `MessageRef` API.#[test]fn trailer_as_sole_body_content() { let msg = MessageRef::from_bytes(b"Fix the thing\n\nSigned-off-by: Alice <alice@example.com>\n"); let body = msg.body().expect("body is present"); let trailers: Vec<_> = body.trailers().collect(); assert_eq!(msg.title, "Fix the thing"); assert_eq!(body.without_trailer(), ""); assert_eq!( trailers, vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], );} #[test]fn folded_trailer_as_sole_body_content_via_message_ref() { let msg = MessageRef::from_bytes(b"Fix the thing\n\nAcked-by: Alice\n continuation line\n"); let body = msg.body().expect("body is present"); assert_eq!(msg.title, "Fix the thing"); assert_eq!(body.without_trailer(), ""); let trailers: Vec<_> = body.trailers().collect(); assert_eq!( trailers, vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], );} mod body { use bstr::ByteSlice; use gix_object::commit::{ MessageRef, message::{BodyRef, body::TrailerRef}, }; fn body(input: &str) -> BodyRef<'_> { BodyRef::from_bytes(input.as_bytes()) } #[test] fn created_manually_is_the_same_as_through_message_ref() { assert_eq!( MessageRef { title: "title unused".into(), body: Some("hello".into()), } .body() .expect("present"), BodyRef::from_bytes("hello".as_bytes()) ); } #[test] fn no_trailer() { let input = "foo\nbar"; assert_eq!(body(input).as_ref(), input); assert_eq!(body(input).without_trailer(), input); } #[test] fn no_trailer_after_a_few_paragraphs_empty_last_block() { let input = "foo\nbar\n\nbar\n\nbaz\n\n"; assert_eq!(body(input).as_ref(), input); } #[test] fn no_trailer_after_a_few_paragraphs_empty_last_block_windows() { let input = "foo\nbar\n\nbar\n\nbaz\r\n\r\n"; assert_eq!(body(input).as_ref(), input); } #[test] fn no_trailer_after_a_few_paragraphs() { let input = "foo\nbar\n\nbar\n\nbaz"; assert_eq!(body(input).as_ref(), input); } #[test] fn single_trailer_after_a_few_paragraphs() { let input = "foo\nbar\n\nbar\n\nbaz\n\ntoken: value"; let body = body(input); assert_eq!(body.as_ref(), "foo\nbar\n\nbar\n\nbaz"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "token".into(), value: b"value".as_bstr().into() }] ); } #[test] fn generic_trailers_mixed_with_prose_after_a_few_paragraphs_are_not_a_trailer_block() { let input = "foo\nbar\n\nbar\n\nbaz\n\na: b\ncannot parse this\r\nc: d\n"; let body = body(input); assert_eq!(body.as_ref(), input); assert_eq!(body.trailers().collect::<Vec<_>>(), vec![]); } #[test] fn no_trailer_after_a_paragraph_windows() { let input = "foo\nbar\n\nbar\r\n\r\nbaz"; assert_eq!(body(input).as_ref(), input); } /// A commit whose body is *only* trailers (no preceding body paragraph) should /// have its trailers detected, matching the behaviour of `git interpret-trailers`. /// /// This arises naturally when a commit message has a subject line followed /// immediately by trailers and no other body text, e.g.: /// /// ```text /// Fix the thing /// /// Signed-off-by: Alice <alice@example.com> /// ``` /// /// The full message bytes are `"Fix the thing\n\nSigned-off-by: …"`. /// `MessageRef::from_bytes` splits at the first `\n\n`, yielding the body /// `"Signed-off-by: …"` — which contains no second `\n\n`. Prior to this /// fix `BodyRef::from_bytes` therefore returned zero trailers for such /// messages, diverging from `git interpret-trailers --parse`. #[test] fn trailer_as_sole_body_content() { let input = "Signed-off-by: Alice <alice@example.com>\nCo-authored-by: Bob <bob@example.com>\nAssisted-by: Agent <agent@example.com>"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Co-authored-by".into(), value: b"Bob <bob@example.com>".as_bstr().into(), }, TrailerRef { token: "Assisted-by".into(), value: b"Agent <agent@example.com>".as_bstr().into(), }, ], ); assert_eq!( body.trailers() .assisted_by() .map(|trailer| trailer.value) .collect::<Vec<_>>(), [b"Agent <agent@example.com>".as_bstr()], "assisted-by trailers have the same convenience filtering as co-authored-by" ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } /// Generic trailer-looking lines mixed with prose in the same paragraph do not /// form a trailer block without a recognized Git prefix. #[test] fn body_text_then_generic_trailer_without_blank_line_is_not_a_trailer() { let input = "some body text\ntoken: value"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "generic trailer-like lines are not enough to form a trailer block" ); } /// Git recognizes `Signed-off-by` as a special trailer prefix, so a footer block /// that mixes prose with such trailers still parses even without an extra blank /// line inside the body. #[test] fn body_text_then_recognized_trailer_without_blank_line_is_a_trailer_block() { let input = "some body text\nSigned-off-by: Alice <alice@example.com>"; let body = body(input); assert_eq!(body.without_trailer(), "", "the whole body is the trailer block"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], ); } /// A body whose first line looks like a trailer but whose subsequent /// lines are plain prose must not be treated as a trailer block. #[test] fn trailer_like_first_line_followed_by_prose_is_not_a_trailer() { let input = "Note: this is not a trailer despite the colon\nmore explanation"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "not a trailer block when non-trailer lines are present" ); } #[test] fn trailer_as_sole_body_content_with_folded_value() { let input = "Acked-by: Alice\n continuation line"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } #[test] fn trailer_as_sole_body_content_with_space_before_separator() { let input = "Acked-by : Alice"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice".as_bstr().into(), }], ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } #[test] fn mixed_footer_with_recognized_prefix_and_prose_is_a_trailer_block() { let input = "not a trailer\nSigned-off-by: Alice <alice@example.com>\nanother note\nSigned-off-by: Bob <bob@example.com>"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Signed-off-by".into(), value: b"Bob <bob@example.com>".as_bstr().into(), }, ], ); assert_eq!( body.without_trailer(), "", "the entire body is the trailer block, even though not everything can be parsed" ); } #[test] fn message_blocks_preserve_raw_messages_and_group_contiguous_trailers() { let input = "body paragraph\n\nnot a trailer\n\Signed-off-by: Alice <alice@example.com>\n\Reviewed-by: Carol <carol@example.com>\n\another note\r\n\Signed-off-by: Bob <bob@example.com>\n\\x20continuation"; let body = body(input); let blocks = body.message_blocks().collect::<Vec<_>>(); assert_eq!( blocks.len(), 2, "prose between trailer runs starts another block: {:?}", blocks.iter().map(|block| block.message).collect::<Vec<_>>() ); assert_eq!( blocks[0].message, "body paragraph\n\nnot a trailer\n", "message bytes and separator whitespace are preserved" ); assert_eq!( blocks[0].trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Reviewed-by".into(), value: b"Carol <carol@example.com>".as_bstr().into(), }, ], "adjacent trailers share a block" ); assert_eq!( blocks[1].message, "another note\r\n", "message line endings remain untouched" ); assert_eq!( blocks[1].trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Bob <bob@example.com> continuation".as_bstr().into(), }], "folded trailers remain handled by the existing iterator" ); } #[test] fn recognized_prefix_footer_at_exactly_twenty_five_percent_is_a_trailer_block() { let input = "Signed-off-by: Alice <alice@example.com>\n\not a trailer 1\n\not a trailer 2\n\not a trailer 3"; let body = body(input); assert_eq!(body.without_trailer(), "", "the whole body is the trailer block"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], ); } #[test] fn recognized_prefix_footer_below_twenty_five_percent_is_not_a_trailer_block() { let input = "Signed-off-by: Alice <alice@example.com>\n\not a trailer 1\n\not a trailer 2\n\not a trailer 3\n\not a trailer 4"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!(body.trailers().collect::<Vec<_>>(), vec![]); } #[test] fn mixed_footer_with_only_generic_trailers_and_prose_is_not_a_trailer_block() { let input = "a: b\nnot a trailer\nc: d"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "generic trailers mixed with prose do not form a trailer block" ); } #[test] fn folded_trailer_after_body_paragraph() { let input = "body paragraph\n\nAcked-by: Alice\n continuation line"; let body = body(input); assert_eq!(body.without_trailer(), "body paragraph"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], ); } #[test] fn trailer_as_sole_body_content_with_folded_value_windows() { let input = "Acked-by: Alice\r\n continuation line\r\n"; let body = body(input); assert_eq!(Lines 1–500 of 582. Load more
use bstr::ByteSlice; use gix_object::commit::{MessageRef, message::body::TrailerRef}; #[test] fn only_title_no_trailing_newline() { let msg = MessageRef::from_bytes(b"hello there"); assert_eq!( msg, MessageRef { title: b"hello there".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there"); } #[test] fn title_and_body() { let msg = MessageRef::from_bytes(b"hello\n\nthere"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: Some("there".into()) } ); assert_eq!(msg.summary().as_ref(), "hello"); } #[test] fn title_and_body_inconsistent_newlines() { let msg = MessageRef::from_bytes(b"hello\n\r\nthere"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: Some("there".into()) } ); assert_eq!(msg.summary().as_ref(), "hello"); } #[test] fn only_title_trailing_newline_is_retained() { let msg = MessageRef::from_bytes(b"hello there\n"); assert_eq!( msg, MessageRef { title: b"hello there\n".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there"); } #[test] fn only_title_trailing_windows_newline_is_retained() { let msg = MessageRef::from_bytes(b"hello there\r\n"); assert_eq!( msg, MessageRef { title: b"hello there\r\n".as_bstr(), body: None } ); assert_eq!(msg.summary().as_ref(), "hello there"); } #[test] fn title_with_whitespace_and_body() { let msg = MessageRef::from_bytes(b"hello \t \r\n there\nanother line\n\nthe body\n\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \t \r\n there\nanother line".as_bstr(), body: Some(b"the body\n\n".as_bstr()) } ); } #[test] fn title_with_more_whitespace_and_body() { let msg = MessageRef::from_bytes(b"hello \r\r\r\n there\nanother line\n\nthe body\n\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \r\r\r\n there\nanother line".as_bstr(), body: Some(b"the body\n\n".as_bstr()) } ); } #[test] fn title_with_whitespace_and_body_windows_lineending() { let msg = MessageRef::from_bytes(b"hello \r\n \r\n there\nanother line\r\n\r\nthe body\n\r\n"); assert_eq!(msg.summary().as_ref(), "hello there another line"); assert_eq!( msg, MessageRef { title: b"hello \r\n \r\n there\nanother line".as_bstr(), body: Some(b"the body\n\r\n".as_bstr()) } ); } #[test] fn title_with_separator_and_empty_body() { let msg = MessageRef::from_bytes(b"hello\n\n"); assert_eq!(msg.summary().as_ref(), "hello"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: None } ); } #[test] fn title_with_windows_separator_and_empty_body() { let msg = MessageRef::from_bytes(b"hello\r\n\r\n"); assert_eq!(msg.summary().as_ref(), "hello"); assert_eq!( msg, MessageRef { title: b"hello".as_bstr(), body: None } ); } /// Via `MessageRef`: a subject-only message with a trailer immediately /// after the blank line (the common case in the wild) must surface the /// trailer through the public `MessageRef` API. #[test] fn trailer_as_sole_body_content() { let msg = MessageRef::from_bytes(b"Fix the thing\n\nSigned-off-by: Alice <alice@example.com>\n"); let body = msg.body().expect("body is present"); let trailers: Vec<_> = body.trailers().collect(); assert_eq!(msg.title, "Fix the thing"); assert_eq!(body.without_trailer(), ""); assert_eq!( trailers, vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], ); } #[test] fn folded_trailer_as_sole_body_content_via_message_ref() { let msg = MessageRef::from_bytes(b"Fix the thing\n\nAcked-by: Alice\n continuation line\n"); let body = msg.body().expect("body is present"); assert_eq!(msg.title, "Fix the thing"); assert_eq!(body.without_trailer(), ""); let trailers: Vec<_> = body.trailers().collect(); assert_eq!( trailers, vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], ); } mod body { use bstr::ByteSlice; use gix_object::commit::{ MessageRef, message::{BodyRef, body::TrailerRef}, }; fn body(input: &str) -> BodyRef<'_> { BodyRef::from_bytes(input.as_bytes()) } #[test] fn created_manually_is_the_same_as_through_message_ref() { assert_eq!( MessageRef { title: "title unused".into(), body: Some("hello".into()), } .body() .expect("present"), BodyRef::from_bytes("hello".as_bytes()) ); } #[test] fn no_trailer() { let input = "foo\nbar"; assert_eq!(body(input).as_ref(), input); assert_eq!(body(input).without_trailer(), input); } #[test] fn no_trailer_after_a_few_paragraphs_empty_last_block() { let input = "foo\nbar\n\nbar\n\nbaz\n\n"; assert_eq!(body(input).as_ref(), input); } #[test] fn no_trailer_after_a_few_paragraphs_empty_last_block_windows() { let input = "foo\nbar\n\nbar\n\nbaz\r\n\r\n"; assert_eq!(body(input).as_ref(), input); } #[test] fn no_trailer_after_a_few_paragraphs() { let input = "foo\nbar\n\nbar\n\nbaz"; assert_eq!(body(input).as_ref(), input); } #[test] fn single_trailer_after_a_few_paragraphs() { let input = "foo\nbar\n\nbar\n\nbaz\n\ntoken: value"; let body = body(input); assert_eq!(body.as_ref(), "foo\nbar\n\nbar\n\nbaz"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "token".into(), value: b"value".as_bstr().into() }] ); } #[test] fn generic_trailers_mixed_with_prose_after_a_few_paragraphs_are_not_a_trailer_block() { let input = "foo\nbar\n\nbar\n\nbaz\n\na: b\ncannot parse this\r\nc: d\n"; let body = body(input); assert_eq!(body.as_ref(), input); assert_eq!(body.trailers().collect::<Vec<_>>(), vec![]); } #[test] fn no_trailer_after_a_paragraph_windows() { let input = "foo\nbar\n\nbar\r\n\r\nbaz"; assert_eq!(body(input).as_ref(), input); } /// A commit whose body is *only* trailers (no preceding body paragraph) should /// have its trailers detected, matching the behaviour of `git interpret-trailers`. /// /// This arises naturally when a commit message has a subject line followed /// immediately by trailers and no other body text, e.g.: /// /// ```text /// Fix the thing /// /// Signed-off-by: Alice <alice@example.com> /// ``` /// /// The full message bytes are `"Fix the thing\n\nSigned-off-by: …"`. /// `MessageRef::from_bytes` splits at the first `\n\n`, yielding the body /// `"Signed-off-by: …"` — which contains no second `\n\n`. Prior to this /// fix `BodyRef::from_bytes` therefore returned zero trailers for such /// messages, diverging from `git interpret-trailers --parse`. #[test] fn trailer_as_sole_body_content() { let input = "Signed-off-by: Alice <alice@example.com>\nCo-authored-by: Bob <bob@example.com>\nAssisted-by: Agent <agent@example.com>"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Co-authored-by".into(), value: b"Bob <bob@example.com>".as_bstr().into(), }, TrailerRef { token: "Assisted-by".into(), value: b"Agent <agent@example.com>".as_bstr().into(), }, ], ); assert_eq!( body.trailers() .assisted_by() .map(|trailer| trailer.value) .collect::<Vec<_>>(), [b"Agent <agent@example.com>".as_bstr()], "assisted-by trailers have the same convenience filtering as co-authored-by" ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } /// Generic trailer-looking lines mixed with prose in the same paragraph do not /// form a trailer block without a recognized Git prefix. #[test] fn body_text_then_generic_trailer_without_blank_line_is_not_a_trailer() { let input = "some body text\ntoken: value"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "generic trailer-like lines are not enough to form a trailer block" ); } /// Git recognizes `Signed-off-by` as a special trailer prefix, so a footer block /// that mixes prose with such trailers still parses even without an extra blank /// line inside the body. #[test] fn body_text_then_recognized_trailer_without_blank_line_is_a_trailer_block() { let input = "some body text\nSigned-off-by: Alice <alice@example.com>"; let body = body(input); assert_eq!(body.without_trailer(), "", "the whole body is the trailer block"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], ); } /// A body whose first line looks like a trailer but whose subsequent /// lines are plain prose must not be treated as a trailer block. #[test] fn trailer_like_first_line_followed_by_prose_is_not_a_trailer() { let input = "Note: this is not a trailer despite the colon\nmore explanation"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "not a trailer block when non-trailer lines are present" ); } #[test] fn trailer_as_sole_body_content_with_folded_value() { let input = "Acked-by: Alice\n continuation line"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } #[test] fn trailer_as_sole_body_content_with_space_before_separator() { let input = "Acked-by : Alice"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice".as_bstr().into(), }], ); assert_eq!(body.without_trailer(), "", "body-without-trailer must be empty"); } #[test] fn mixed_footer_with_recognized_prefix_and_prose_is_a_trailer_block() { let input = "not a trailer\nSigned-off-by: Alice <alice@example.com>\nanother note\nSigned-off-by: Bob <bob@example.com>"; let body = body(input); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Signed-off-by".into(), value: b"Bob <bob@example.com>".as_bstr().into(), }, ], ); assert_eq!( body.without_trailer(), "", "the entire body is the trailer block, even though not everything can be parsed" ); } #[test] fn message_blocks_preserve_raw_messages_and_group_contiguous_trailers() { let input = "body paragraph\n\nnot a trailer\n\ Signed-off-by: Alice <alice@example.com>\n\ Reviewed-by: Carol <carol@example.com>\n\ another note\r\n\ Signed-off-by: Bob <bob@example.com>\n\ \x20continuation"; let body = body(input); let blocks = body.message_blocks().collect::<Vec<_>>(); assert_eq!( blocks.len(), 2, "prose between trailer runs starts another block: {:?}", blocks.iter().map(|block| block.message).collect::<Vec<_>>() ); assert_eq!( blocks[0].message, "body paragraph\n\nnot a trailer\n", "message bytes and separator whitespace are preserved" ); assert_eq!( blocks[0].trailers().collect::<Vec<_>>(), vec![ TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }, TrailerRef { token: "Reviewed-by".into(), value: b"Carol <carol@example.com>".as_bstr().into(), }, ], "adjacent trailers share a block" ); assert_eq!( blocks[1].message, "another note\r\n", "message line endings remain untouched" ); assert_eq!( blocks[1].trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Bob <bob@example.com> continuation".as_bstr().into(), }], "folded trailers remain handled by the existing iterator" ); } #[test] fn recognized_prefix_footer_at_exactly_twenty_five_percent_is_a_trailer_block() { let input = "Signed-off-by: Alice <alice@example.com>\n\ not a trailer 1\n\ not a trailer 2\n\ not a trailer 3"; let body = body(input); assert_eq!(body.without_trailer(), "", "the whole body is the trailer block"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Signed-off-by".into(), value: b"Alice <alice@example.com>".as_bstr().into(), }], ); } #[test] fn recognized_prefix_footer_below_twenty_five_percent_is_not_a_trailer_block() { let input = "Signed-off-by: Alice <alice@example.com>\n\ not a trailer 1\n\ not a trailer 2\n\ not a trailer 3\n\ not a trailer 4"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!(body.trailers().collect::<Vec<_>>(), vec![]); } #[test] fn mixed_footer_with_only_generic_trailers_and_prose_is_not_a_trailer_block() { let input = "a: b\nnot a trailer\nc: d"; let body = body(input); assert_eq!(body.without_trailer(), input, "must be returned unchanged"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![], "generic trailers mixed with prose do not form a trailer block" ); } #[test] fn folded_trailer_after_body_paragraph() { let input = "body paragraph\n\nAcked-by: Alice\n continuation line"; let body = body(input); assert_eq!(body.without_trailer(), "body paragraph"); assert_eq!( body.trailers().collect::<Vec<_>>(), vec![TrailerRef { token: "Acked-by".into(), value: b"Alice continuation line".as_bstr().into(), }], ); } #[test] fn trailer_as_sole_body_content_with_folded_value_windows() { let input = "Acked-by: Alice\r\n continuation line\r\n"; let body = body(input); assert_eq!(