mirror of
https://github.com/taigrr/github-to-signal.git
synced 2026-04-15 01:31:03 -07:00
- Bump go-github from v70 to v84 - Bump githubevents from v2.2.0 to v2.15.2 - Update signalcli to latest - Update Go toolchain to 1.26.2 - Add comprehensive tests for all format functions - Add tests for firstLine, truncate, splitMessage helpers - Add edge case tests (singular commit, closed issue body suppression)
508 lines
12 KiB
Go
508 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/go-github/v84/github"
|
|
)
|
|
|
|
func strPtr(s string) *string { return &s }
|
|
func intPtr(i int) *int { return &i }
|
|
|
|
func TestFormatPush(t *testing.T) {
|
|
event := &github.PushEvent{
|
|
Ref: strPtr("refs/heads/main"),
|
|
Repo: &github.PushEventRepository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Pusher: &github.CommitAuthor{
|
|
Name: strPtr("tai"),
|
|
},
|
|
Commits: []*github.HeadCommit{
|
|
{
|
|
ID: strPtr("abc1234567890"),
|
|
Message: strPtr("feat: initial commit"),
|
|
},
|
|
{
|
|
ID: strPtr("def4567890123"),
|
|
Message: strPtr("fix: typo\n\nLonger description here"),
|
|
},
|
|
},
|
|
}
|
|
|
|
got := formatPush(event)
|
|
if got == "" {
|
|
t.Fatal("formatPush returned empty string")
|
|
}
|
|
|
|
// Check key parts are present
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"tai pushed 2 commits to main",
|
|
"abc1234",
|
|
"feat: initial commit",
|
|
"def4567",
|
|
"fix: typo",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatPush missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatPushSingular(t *testing.T) {
|
|
event := &github.PushEvent{
|
|
Ref: strPtr("refs/heads/dev"),
|
|
Repo: &github.PushEventRepository{
|
|
FullName: strPtr("taigrr/repo"),
|
|
},
|
|
Pusher: &github.CommitAuthor{
|
|
Name: strPtr("tai"),
|
|
},
|
|
Commits: []*github.HeadCommit{
|
|
{
|
|
ID: strPtr("aaa1111222233"),
|
|
Message: strPtr("docs: update readme"),
|
|
},
|
|
},
|
|
}
|
|
|
|
got := formatPush(event)
|
|
if !contains(got, "1 commit to dev") {
|
|
t.Errorf("expected singular 'commit', got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatIssue(t *testing.T) {
|
|
event := &github.IssuesEvent{
|
|
Action: strPtr("opened"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("contributor"),
|
|
},
|
|
Issue: &github.Issue{
|
|
Number: intPtr(42),
|
|
Title: strPtr("Bug: something broken"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/issues/42"),
|
|
Body: strPtr("Steps to reproduce..."),
|
|
},
|
|
}
|
|
|
|
got := formatIssue(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"contributor",
|
|
"opened",
|
|
"#42",
|
|
"Bug: something broken",
|
|
"https://github.com/taigrr/example/issues/42",
|
|
"Steps to reproduce...",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatIssue missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatIssueComment(t *testing.T) {
|
|
event := &github.IssueCommentEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("reviewer"),
|
|
},
|
|
Issue: &github.Issue{
|
|
Number: intPtr(10),
|
|
Title: strPtr("Feature request"),
|
|
},
|
|
Comment: &github.IssueComment{
|
|
Body: strPtr("Looks good to me!"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/issues/10#comment-1"),
|
|
},
|
|
}
|
|
|
|
got := formatIssueComment(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"reviewer",
|
|
"#10",
|
|
"Feature request",
|
|
"Looks good to me!",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatIssueComment missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatPR(t *testing.T) {
|
|
event := &github.PullRequestEvent{
|
|
Action: strPtr("opened"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("author"),
|
|
},
|
|
PullRequest: &github.PullRequest{
|
|
Number: intPtr(5),
|
|
Title: strPtr("Add feature X"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/pull/5"),
|
|
Body: strPtr("This PR adds feature X"),
|
|
},
|
|
}
|
|
|
|
got := formatPR(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"author",
|
|
"opened",
|
|
"PR #5",
|
|
"Add feature X",
|
|
"This PR adds feature X",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatPR missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatPRReview(t *testing.T) {
|
|
event := &github.PullRequestReviewEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("reviewer"),
|
|
},
|
|
PullRequest: &github.PullRequest{
|
|
Number: intPtr(5),
|
|
Title: strPtr("Add feature X"),
|
|
},
|
|
Review: &github.PullRequestReview{
|
|
State: strPtr("approved"),
|
|
Body: strPtr("Ship it!"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/pull/5#pullrequestreview-1"),
|
|
},
|
|
}
|
|
|
|
got := formatPRReview(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"reviewer",
|
|
"approved",
|
|
"PR #5",
|
|
"Ship it!",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatPRReview missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatPRReviewComment(t *testing.T) {
|
|
event := &github.PullRequestReviewCommentEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("reviewer"),
|
|
},
|
|
PullRequest: &github.PullRequest{
|
|
Number: intPtr(5),
|
|
Title: strPtr("Add feature X"),
|
|
},
|
|
Comment: &github.PullRequestComment{
|
|
Body: strPtr("Nit: rename this variable"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/pull/5#discussion_r1"),
|
|
},
|
|
}
|
|
|
|
got := formatPRReviewComment(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"reviewer",
|
|
"PR #5",
|
|
"Nit: rename this variable",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatPRReviewComment missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatRelease(t *testing.T) {
|
|
event := &github.ReleaseEvent{
|
|
Action: strPtr("published"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("tai"),
|
|
},
|
|
Release: &github.RepositoryRelease{
|
|
TagName: strPtr("v1.0.0"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/releases/tag/v1.0.0"),
|
|
},
|
|
}
|
|
|
|
got := formatRelease(event)
|
|
checks := []string{
|
|
"[taigrr/example]",
|
|
"tai",
|
|
"published",
|
|
"v1.0.0",
|
|
}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatRelease missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatStar(t *testing.T) {
|
|
event := &github.StarEvent{
|
|
Action: strPtr("created"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
StargazersCount: intPtr(100),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("fan"),
|
|
},
|
|
}
|
|
|
|
got := formatStar(event)
|
|
if !contains(got, "fan") || !contains(got, "starred") || !contains(got, "100") {
|
|
t.Errorf("formatStar unexpected output:\n%s", got)
|
|
}
|
|
|
|
// Test unstar
|
|
event.Action = strPtr("deleted")
|
|
got = formatStar(event)
|
|
if !contains(got, "unstarred") {
|
|
t.Errorf("formatStar deleted should say 'unstarred', got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatFork(t *testing.T) {
|
|
event := &github.ForkEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Forkee: &github.Repository{
|
|
FullName: strPtr("user/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("user"),
|
|
},
|
|
}
|
|
|
|
got := formatFork(event)
|
|
checks := []string{"taigrr/example", "user", "forked", "user/example"}
|
|
for _, c := range checks {
|
|
if !contains(got, c) {
|
|
t.Errorf("formatFork missing %q in output:\n%s", c, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatWorkflowRun(t *testing.T) {
|
|
event := &github.WorkflowRunEvent{
|
|
Action: strPtr("completed"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
WorkflowRun: &github.WorkflowRun{
|
|
Name: strPtr("CI"),
|
|
Conclusion: strPtr("success"),
|
|
HeadBranch: strPtr("main"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/actions/runs/1"),
|
|
},
|
|
}
|
|
|
|
got := formatWorkflowRun(event)
|
|
if !contains(got, "✅") || !contains(got, "CI") || !contains(got, "success") {
|
|
t.Errorf("formatWorkflowRun success unexpected output:\n%s", got)
|
|
}
|
|
|
|
// Test failure
|
|
event.WorkflowRun.Conclusion = strPtr("failure")
|
|
got = formatWorkflowRun(event)
|
|
if !contains(got, "❌") {
|
|
t.Errorf("formatWorkflowRun failure should have ❌, got:\n%s", got)
|
|
}
|
|
|
|
// Test cancelled
|
|
event.WorkflowRun.Conclusion = strPtr("cancelled")
|
|
got = formatWorkflowRun(event)
|
|
if !contains(got, "⚠️") {
|
|
t.Errorf("formatWorkflowRun cancelled should have ⚠️, got:\n%s", got)
|
|
}
|
|
|
|
// Test non-completed action returns empty
|
|
event.Action = strPtr("requested")
|
|
got = formatWorkflowRun(event)
|
|
if got != "" {
|
|
t.Errorf("formatWorkflowRun non-completed should return empty, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatCreate(t *testing.T) {
|
|
event := &github.CreateEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("tai"),
|
|
},
|
|
RefType: strPtr("branch"),
|
|
Ref: strPtr("feature-x"),
|
|
}
|
|
|
|
got := formatCreate(event)
|
|
if !contains(got, "created") || !contains(got, "branch") || !contains(got, "feature-x") {
|
|
t.Errorf("formatCreate unexpected output:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatDelete(t *testing.T) {
|
|
event := &github.DeleteEvent{
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("tai"),
|
|
},
|
|
RefType: strPtr("tag"),
|
|
Ref: strPtr("v0.1.0"),
|
|
}
|
|
|
|
got := formatDelete(event)
|
|
if !contains(got, "deleted") || !contains(got, "tag") || !contains(got, "v0.1.0") {
|
|
t.Errorf("formatDelete unexpected output:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestFirstLine(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{"single line", "single line"},
|
|
{"first\nsecond\nthird", "first"},
|
|
{"", ""},
|
|
{"trailing\n", "trailing"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := firstLine(tt.input); got != tt.want {
|
|
t.Errorf("firstLine(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTruncate(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
maxLen int
|
|
want string
|
|
}{
|
|
{"short", 10, "short"},
|
|
{"exactly10!", 10, "exactly10!"},
|
|
{"this is too long", 10, "this is to..."},
|
|
{"", 5, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := truncate(tt.input, tt.maxLen); got != tt.want {
|
|
t.Errorf("truncate(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSplitMessage(t *testing.T) {
|
|
// Short message — no split
|
|
short := "hello"
|
|
chunks := splitMessage(short)
|
|
if len(chunks) != 1 || chunks[0] != short {
|
|
t.Errorf("splitMessage short: got %v", chunks)
|
|
}
|
|
|
|
// Exactly at limit
|
|
exact := string(make([]byte, maxMessageLen))
|
|
for i := range exact {
|
|
exact = exact[:i] + "a" + exact[i+1:]
|
|
}
|
|
chunks = splitMessage(exact)
|
|
if len(chunks) != 1 {
|
|
t.Errorf("splitMessage exact: got %d chunks, want 1", len(chunks))
|
|
}
|
|
|
|
// Over limit — should split
|
|
long := make([]byte, maxMessageLen+500)
|
|
for i := range long {
|
|
long[i] = 'x'
|
|
}
|
|
// Insert a newline near the boundary for clean split
|
|
long[maxMessageLen-10] = '\n'
|
|
chunks = splitMessage(string(long))
|
|
if len(chunks) < 2 {
|
|
t.Errorf("splitMessage long: expected 2+ chunks, got %d", len(chunks))
|
|
}
|
|
// Verify all content is preserved
|
|
total := 0
|
|
for _, c := range chunks {
|
|
total += len(c)
|
|
}
|
|
// Account for whitespace trimming
|
|
if total < maxMessageLen {
|
|
t.Errorf("splitMessage long: total content %d seems too small", total)
|
|
}
|
|
}
|
|
|
|
func TestFormatIssueClosedNoBody(t *testing.T) {
|
|
event := &github.IssuesEvent{
|
|
Action: strPtr("closed"),
|
|
Repo: &github.Repository{
|
|
FullName: strPtr("taigrr/example"),
|
|
},
|
|
Sender: &github.User{
|
|
Login: strPtr("tai"),
|
|
},
|
|
Issue: &github.Issue{
|
|
Number: intPtr(1),
|
|
Title: strPtr("Old bug"),
|
|
HTMLURL: strPtr("https://github.com/taigrr/example/issues/1"),
|
|
Body: strPtr("Some body that should not appear"),
|
|
},
|
|
}
|
|
|
|
got := formatIssue(event)
|
|
// Body should NOT be included for non-opened actions
|
|
if contains(got, "Some body that should not appear") {
|
|
t.Errorf("formatIssue closed should not include body, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
|
|
(len(substr) > 0 && containsStr(s, substr)))
|
|
}
|
|
|
|
func containsStr(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|