package changelog import ( "bytes" "fmt" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestSanitizeSubject(t *testing.T) { t.Parallel() tests := []struct { name string subject string want string }{ { name: "merge prefix with quotes", subject: "Merge pull request 'Add classes overhaul (#123)'", want: "Add classes overhaul", }, { name: "plain subject", subject: "Add classes overhaul (#123)", want: "Add classes overhaul", }, { name: "double quotes", subject: `Merge pull request "Add classes overhaul (#123)"`, want: "Add classes overhaul", }, { name: "direct push", subject: "Fix direct push handling", want: "Fix direct push handling", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := sanitizeSubject(tt.subject); got != tt.want { t.Fatalf("sanitizeSubject(%q) = %q, want %q", tt.subject, got, tt.want) } }) } } func TestShortCommitHash(t *testing.T) { t.Parallel() if got := shortCommitHash("1234567890abcdef"); got != "1234567" { t.Fatalf("shortCommitHash() = %q, want %q", got, "1234567") } if got := shortCommitHash("1234567"); got != "1234567" { t.Fatalf("shortCommitHash() preserved %q unexpectedly as %q", "1234567", got) } } func TestOwnerAndRepoFromURL(t *testing.T) { t.Parallel() owner, repo, err := ownerAndRepoFromURL("https://gitea.example.test/org/repo.git") if err != nil { t.Fatalf("ownerAndRepoFromURL returned error: %v", err) } if owner != "org" || repo != "repo" { t.Fatalf("ownerAndRepoFromURL returned %q/%q", owner, repo) } } func TestDeriveAPIBaseURL(t *testing.T) { t.Parallel() got := deriveAPIBaseURL("https://gitea.example.test/org/repo") want := "https://gitea.example.test/api/v1" if got != want { t.Fatalf("deriveAPIBaseURL() = %q, want %q", got, want) } } func TestNormalizeAPIBaseURL(t *testing.T) { t.Parallel() tests := []struct { input string want string }{ {input: "https://gitea.example.test", want: "https://gitea.example.test/api/v1"}, {input: "https://gitea.example.test/api/v1", want: "https://gitea.example.test/api/v1"}, } for _, tt := range tests { if got := normalizeAPIBaseURL(tt.input); got != tt.want { t.Fatalf("normalizeAPIBaseURL(%q) = %q, want %q", tt.input, got, tt.want) } } } func TestGenerateIncludesPullRequestsAndDirectPushes(t *testing.T) { t.Parallel() repoRoot := t.TempDir() runGit(t, repoRoot, "init") runGit(t, repoRoot, "config", "user.name", "Test User") runGit(t, repoRoot, "config", "user.email", "test@example.com") writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "initial\n") runGit(t, repoRoot, "add", ".") runGit(t, repoRoot, "commit", "-m", "Initial import") runGit(t, repoRoot, "tag", "v1.0.0") writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "direct\n") runGit(t, repoRoot, "add", ".") runGit(t, repoRoot, "commit", "-m", "Fix direct push handling") directHash := strings.TrimSpace(runGit(t, repoRoot, "rev-parse", "HEAD")) writeFile(t, filepath.Join(repoRoot, "content", "entry.txt"), "pull\n") runGit(t, repoRoot, "add", ".") runGit(t, repoRoot, "commit", "-m", "Add release summary (#12)") runGit(t, repoRoot, "tag", "v1.1.0") configPath := filepath.Join(repoRoot, "scripts", "changelog.json") writeFile(t, configPath, `{ "repo_url": "REPO_URL", "categories": [ { "title": "Content", "sections": [ { "title": "Entries", "paths": ["content/"] } ] } ] }`) var pullRequests int server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/repos/org/repo/pulls/12" { t.Fatalf("unexpected API path %q", r.URL.Path) } pullRequests++ w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"user":{"full_name":"Patch Author","login":"patch-author"}}`) })) defer server.Close() repoURL := server.URL + "/org/repo" configData, err := os.ReadFile(configPath) if err != nil { t.Fatalf("read changelog config: %v", err) } configData = bytes.ReplaceAll(configData, []byte("REPO_URL"), []byte(repoURL)) if err := os.WriteFile(configPath, configData, 0o644); err != nil { t.Fatalf("write changelog config: %v", err) } var stdout bytes.Buffer err = Generate(Options{ RepoRoot: repoRoot, ConfigPath: configPath, CurrentTag: "v1.1.0", PreviousTag: "v1.0.0", APIBaseURL: server.URL + "/api/v1", Stdout: &stdout, }) if err != nil { t.Fatalf("Generate() returned error: %v", err) } rendered := stdout.String() for _, want := range []string{ "Add release summary", repoURL + "/pulls/12", "Patch Author", "Fix direct push handling", repoURL + "/commit/" + directHash, "Test User", } { if !strings.Contains(rendered, want) { t.Errorf("rendered changelog missing %q:\n%s", want, rendered) } } if pullRequests != 1 { t.Fatalf("expected 1 pull lookup, got %d", pullRequests) } } func runGit(t *testing.T, repoRoot string, args ...string) string { t.Helper() cmd := exec.Command("git", append([]string{"-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false"}, args...)...) cmd.Dir = repoRoot output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %s failed: %v\n%s", strings.Join(args, " "), err, output) } return string(output) } func writeFile(t *testing.T, path string, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("MkdirAll(%q): %v", path, err) } if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("WriteFile(%q): %v", path, err) } }