package changelog import ( "reflect" "strings" "testing" ) var testPeople = People{ "paul@tito.io": "Paul Campbell", "paul@keymail.dev": "Paul Campbell", "bill@keymail.dev": "Bill Horsman", "james@lazyatom.com": "James Adam", "tonywood@keymail.dev": "Tony Wood", "tony@tctl.co.uk": "Tony Wood", } func TestParseIdentity(t *testing.T) { cases := []struct { name, email string person, agent string skip bool }{ {"claude", "claude~paul@keymail.dev", "Paul Campbell", "claude", false}, {"claude~paul@keymail.dev", "claude~paul@keymail.dev", "Paul Campbell", "claude", false}, {"claude-code~bill@keymail.dev", "claude-code~bill@keymail.dev", "Bill Horsman", "claude", false}, {"codex", "codex~paul@keymail.dev", "Paul Campbell", "codex", false}, {"Claude Opus 5.5", "noreply@anthropic.com", "", "claude", false}, {"Claude", "someone@example.com", "", "claude", false}, {"Codex", "codex@example.com", "", "codex", false}, {"Paul Campbell", "Paul@Tito.io", "Paul Campbell", "", false}, {"GitHub", "noreply@github.com", "", "", true}, {"amadan-ledger", "ledger@amadan", "", "", true}, } for _, c := range cases { person, agent, skip, err := ParseIdentity(c.name, c.email, testPeople) if err != nil { t.Fatalf("%s <%s>: %v", c.name, c.email, err) } if person != c.person || agent != c.agent || skip != c.skip { t.Errorf("%s <%s> = (%q, %q, %v), want (%q, %q, %v)", c.name, c.email, person, agent, skip, c.person, c.agent, c.skip) } } } func TestParseIdentityUnknownFails(t *testing.T) { for _, email := range []string{"stranger@example.com", "claude~stranger@keymail.dev"} { _, _, _, err := ParseIdentity("Someone", email, testPeople) if err == nil || !strings.Contains(err.Error(), email[strings.Index(email, "~")+1:]) { t.Errorf("%s: err = %v, want one naming the address", email, err) } } } func TestCoAuthors(t *testing.T) { msg := "subject\n\nbody\n\nCo-Authored-By: Claude Opus 4.6 \nco-authored-by: Bill Horsman \n" got := CoAuthors(msg) want := [][2]string{{"Claude Opus 4.6", "noreply@anthropic.com"}, {"Bill Horsman", "bill@keymail.dev"}} if !reflect.DeepEqual(got, want) { t.Fatalf("CoAuthors = %v, want %v", got, want) } } func TestTally(t *testing.T) { commits := []CommitIdent{ {"claude", "claude~paul@keymail.dev", "a"}, {"claude", "claude~paul@keymail.dev", "b"}, {"codex", "codex~paul@keymail.dev", "c"}, {"James Adam", "james@lazyatom.com", "d\n\nCo-Authored-By: Claude "}, {"GitHub", "noreply@github.com", "merge"}, } got, err := Tally(commits, testPeople) if err != nil { t.Fatal(err) } want := []Credit{ {Person: "Paul Campbell", Agents: []string{"claude", "codex"}}, {Person: "James Adam", Agents: []string{"claude"}}, } if !reflect.DeepEqual(got, want) { t.Fatalf("Tally = %+v, want %+v", got, want) } } func TestTallyAgentWithNoPersonStandsAlone(t *testing.T) { got, err := Tally([]CommitIdent{{"Claude", "noreply@anthropic.com", "x"}}, testPeople) if err != nil { t.Fatal(err) } want := []Credit{{Agents: []string{"claude"}}} if !reflect.DeepEqual(got, want) { t.Fatalf("Tally = %+v, want %+v", got, want) } } func TestTallyPersonOnly(t *testing.T) { got, err := Tally([]CommitIdent{{"Paul Campbell", "paul@tito.io", "x"}}, testPeople) if err != nil { t.Fatal(err) } want := []Credit{{Person: "Paul Campbell"}} if !reflect.DeepEqual(got, want) { t.Fatalf("Tally = %+v, want %+v", got, want) } } // da866a92 (v0.23.0): an agent commit with a human co-author credits the // agent to its own operator, not to the co-author. func TestTallyKeepsEachIdentitysPairing(t *testing.T) { commits := []CommitIdent{ {"claude", "claude~paul@keymail.dev", "x\n\nCo-Authored-By: Tony Wood "}, {"claude", "claude~paul@keymail.dev", "y"}, } got, err := Tally(commits, testPeople) if err != nil { t.Fatal(err) } want := []Credit{{Person: "Paul Campbell", Agents: []string{"claude"}}, {Person: "Tony Wood"}} if !reflect.DeepEqual(got, want) { t.Fatalf("Tally = %+v, want %+v", got, want) } } // A person's commit with a Claude trailer and a human co-author: Claude // worked with the author. func TestTallyLoneAgentGoesToTheAuthor(t *testing.T) { msg := "x\n\nCo-Authored-By: James Adam \nCo-Authored-By: Claude " got, err := Tally([]CommitIdent{{"Paul Campbell", "paul@tito.io", msg}}, testPeople) if err != nil { t.Fatal(err) } want := []Credit{{Person: "James Adam"}, {Person: "Paul Campbell", Agents: []string{"claude"}}} if !reflect.DeepEqual(got, want) { t.Fatalf("Tally = %+v, want %+v", got, want) } }