use super::*;
fn coauthor_emails(message: &str) -> Vec<String> {
let block = trailer_block(message.as_bytes());
COAUTHOR
.captures_iter(block)
.filter_map(|caps| {
let email = caps.get(2)?;
Some(String::from_utf8_lossy(email.as_bytes()).into_owned())
})
.collect()
}
#[test]
fn coauthor_in_final_trailer_block_is_counted() {
let msg = "pair work\n\nCo-authored-by: Grace Hopper <grace@example.com>\n";
assert_eq!(coauthor_emails(msg), vec!["grace@example.com".to_string()]);
}
#[test]
fn coauthor_alongside_other_trailers_is_counted() {
let msg = "\
fix the thing
Co-authored-by: Real Dev <real@example.com>
Signed-off-by: Ada <ada@example.com>";
assert_eq!(coauthor_emails(msg), vec!["real@example.com".to_string()]);
}
#[test]
fn coauthor_quoted_in_body_is_not_counted() {
let msg = "\
Revert a bad merge
This reverts the merge that introduced the regression. For the record,
the original commit read:
Add pairing helper
Co-authored-by: Eve Quoted <eve@example.com>
Signed-off-by: Ada Lovelace <ada@example.com>";
assert!(
coauthor_emails(msg).is_empty(),
"a body-quoted co-author must not be honoured"
);
}
#[test]
fn coauthor_outside_final_trailer_block_is_not_counted() {
let msg = "\
Document the convention
Co-authored-by: Ghost <ghost@example.com>
This paragraph explains the footer format we standardized on.
Signed-off-by: Real Dev <real@example.com>";
assert!(
coauthor_emails(msg).is_empty(),
"a column-0 co-author outside the final trailer block is not counted"
);
}
#[test]
fn single_paragraph_trailer_only_message_is_counted() {
let msg = "Co-authored-by: Solo <solo@example.com>";
assert_eq!(coauthor_emails(msg), vec!["solo@example.com".to_string()]);
}
#[test]
fn ordinary_single_author_message_yields_no_coauthor() {
let msg = "just a normal commit\n\nwith a body paragraph that explains why.";
assert!(coauthor_emails(msg).is_empty());
}
#[test]
fn empty_message_yields_no_coauthor() {
assert!(coauthor_emails("").is_empty());
}
mod push_if_human {
use super::*;
use crate::vcs::identity::AuthorId;
fn resolver() -> ParticipantResolver<'static> {
let snapshot: &'static gix::mailmap::Snapshot =
Box::leak(Box::new(gix::mailmap::Snapshot::default()));
ParticipantResolver::new(snapshot, None)
}
#[test]
fn keyless_authors_are_dropped_not_collapsed() {
let resolver = resolver();
let mut out = Vec::new();
resolver.push_if_human(&mut out, b"", b"");
resolver.push_if_human(&mut out, b" ", b"");
assert!(
out.is_empty(),
"keyless authors must not anchor any participant: {out:?}"
);
}
#[test]
fn bot_authors_are_excluded_regardless_of_case() {
let snapshot: &'static gix::mailmap::Snapshot =
Box::leak(Box::new(gix::mailmap::Snapshot::default()));
let bots: &'static crate::vcs::identity::BotFilter = Box::leak(Box::new(
crate::vcs::identity::BotFilter::new(crate::vcs::options::DEFAULT_BOT_PATTERN)
.expect("default pattern compiles"),
));
let resolver = ParticipantResolver::new(snapshot, Some(bots));
let mut out = Vec::new();
resolver.push_if_human(
&mut out,
b"dependabot[bot]",
b"dependabot[bot]@users.noreply.github.com",
);
resolver.push_if_human(
&mut out,
b"Dependabot[bot]",
b"Dependabot[bot]@users.noreply.github.com",
);
assert!(
out.is_empty(),
"neither casing of a bot identity may anchor a participant: {out:?}"
);
resolver.push_if_human(&mut out, b"Ada Lovelace", b"ada@example.com");
assert_eq!(out.len(), 1, "a human author is still kept: {out:?}");
}
#[test]
fn named_and_name_only_authors_are_kept() {
let resolver = resolver();
let mut out = Vec::new();
resolver.push_if_human(&mut out, b"Ada", b"ada@example.com");
resolver.push_if_human(&mut out, b"", b"");
resolver.push_if_human(&mut out, b"Grace Hopper", b"");
assert_eq!(out.len(), 2, "two real identities survive: {out:?}");
assert!(out.contains(&AuthorId::new(b"Ada", b"ada@example.com")));
assert!(out.contains(&AuthorId::new(b"Grace Hopper", b"")));
}
}