diff --git a/cfg/config_test.go b/cfg/config_test.go index 7293df6..42a30eb 100644 --- a/cfg/config_test.go +++ b/cfg/config_test.go @@ -17,6 +17,8 @@ limitations under the License. package cfg import ( + "os" + "path" "reflect" "testing" ) @@ -63,3 +65,31 @@ func Test_loadConfig(t *testing.T) { }) } } + +func TestLoadConfig(t *testing.T) { + dir := t.TempDir() + content := "requireBranch: main\nbefore:\n - command: echo\n args:\n - hello world\n" + if err := os.WriteFile(path.Join(dir, defaultConfigFile), []byte(content), 0644); err != nil { + t.Fatal(err) + } + + got, err := LoadConfig(dir) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + want := SinceConfig{RequireBranch: "main", Before: []Hook{{Command: "echo", Args: []string{"hello world"}}}} + if !reflect.DeepEqual(got, want) { + t.Errorf("LoadConfig() got = %v, want %v", got, want) + } +} + +func TestLoadConfig_missingFile(t *testing.T) { + // a directory with no since.yaml should yield an empty config, not an error + got, err := LoadConfig(t.TempDir()) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + if !reflect.DeepEqual(got, SinceConfig{}) { + t.Errorf("LoadConfig() got = %v, want empty config", got) + } +} diff --git a/changelog/changelog_test.go b/changelog/changelog_test.go index 5be87f4..97eabb8 100644 --- a/changelog/changelog_test.go +++ b/changelog/changelog_test.go @@ -26,6 +26,7 @@ import ( "os" "path" "reflect" + "strings" "testing" "time" ) @@ -304,6 +305,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 } } +func TestInitChangelog(t *testing.T) { + repoDir := createTestRepo(t) + // add an unreleased commit so there are changes to render + commitChange(t, repoDir, "README.md", "unreleased\r\n", "feat: unreleased change", time.Now()) + + changelogFile := path.Join(repoDir, "CHANGELOG.md") + + updated, err := InitChangelog(cfg.SinceConfig{}, vcs.CommitConfig{}, changelogFile, vcs.TagOrderSemver, repoDir) + if err != nil { + t.Fatalf("InitChangelog() error = %v", err) + } + + // the rendered changelog should retain the boilerplate header and contain + // at least one version section + if !strings.HasPrefix(updated, "# Changelog") { + t.Errorf("InitChangelog() output missing boilerplate header, got: %q", updated) + } + if !strings.Contains(updated, "## [") { + t.Errorf("InitChangelog() output missing version section, got: %q", updated) + } + + // the file written to disk should match the returned content (with the + // trailing newline WriteChangelog appends) + onDisk, err := os.ReadFile(changelogFile) + if err != nil { + t.Fatal(err) + } + if string(onDisk) != updated+"\n" { + t.Errorf("InitChangelog() file content = %q, want %q", string(onDisk), updated+"\n") + } +} + // createTestRepo creates a test repo with two tags: // 0.0.1 and 0.1.0 // The first tag is created 10 seconds before the second tag. diff --git a/changelog/errors_test.go b/changelog/errors_test.go new file mode 100644 index 0000000..1c96992 --- /dev/null +++ b/changelog/errors_test.go @@ -0,0 +1,39 @@ +package changelog + +import "testing" + +func TestNoChangesError_Error(t *testing.T) { + tests := []struct { + name string + err NoChangesError + want string + }{ + { + name: "excluded commits with start tag", + err: NoChangesError{StartTag: "0.1.0", ExcludedCount: 3}, + want: "no eligible commits since 0.1.0 — 3 commit(s) were excluded by ignore patterns in since.yaml", + }, + { + name: "excluded commits without start tag", + err: NoChangesError{ExcludedCount: 2}, + want: "no eligible commits found — 2 commit(s) were excluded by ignore patterns in since.yaml", + }, + { + name: "start tag with no exclusions", + err: NoChangesError{StartTag: "1.2.3"}, + want: "no commits since 1.2.3", + }, + { + name: "no start tag and no exclusions", + err: NoChangesError{}, + want: "no commits found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.err.Error(); got != tt.want { + t.Errorf("Error() = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/changelog/read_test.go b/changelog/read_test.go index 18bcada..7e739a8 100644 --- a/changelog/read_test.go +++ b/changelog/read_test.go @@ -119,6 +119,71 @@ func TestParseChangelog_withHeader(t *testing.T) { } } +func TestParseChangelog_nonExistent(t *testing.T) { + _, err := ParseChangelog("/nonexistent/CHANGELOG.md", "1.0.0", false) + if err == nil { + t.Error("ParseChangelog() expected error for non-existent file") + } +} + +func Test_readChanges_versionNotFound(t *testing.T) { + lines := []string{ + "# Changelog", + "", + "## [1.0.0] - 2024-01-01", + "- feat: foo", + } + _, err := readChanges(lines, "9.9.9", false) + if err == nil { + t.Fatal("readChanges() expected error for missing version") + } + want := "could not find version 9.9.9 in changelog" + if err.Error() != want { + t.Errorf("readChanges() error = %q, want %q", err.Error(), want) + } +} + +func Test_readChanges_noVersionSections(t *testing.T) { + lines := []string{ + "# Changelog", + "", + "All notable changes are documented here.", + } + _, err := readChanges(lines, "", false) + if err == nil { + t.Fatal("readChanges() expected error when no version sections present") + } + want := "changelog contains no version sections" + if err.Error() != want { + t.Errorf("readChanges() error = %q, want %q", err.Error(), want) + } +} + +func Test_readChanges_lastVersionToEndOfFile(t *testing.T) { + // trailing empty string mirrors ReadFile splitting a file that ends in a + // newline; readChanges relies on it to bound the final section + lines := []string{ + "# Changelog", + "", + "## [1.0.0] - 2024-01-01", + "### Added", + "- feat: foo", + "", + "## [0.9.0] - 2023-12-01", + "### Fixed", + "- fix: bar", + "", + } + got, err := readChanges(lines, "0.9.0", true) + if err != nil { + t.Fatalf("readChanges() error = %v", err) + } + want := []string{"## [0.9.0] - 2023-12-01", "### Fixed", "- fix: bar"} + if !reflect.DeepEqual(got, want) { + t.Errorf("readChanges() = %v, want %v", got, want) + } +} + func Test_readChanges_firstVersion(t *testing.T) { lines := []string{ "# Changelog", diff --git a/semver/versions_test.go b/semver/versions_test.go index 18d46be..19a6dc4 100644 --- a/semver/versions_test.go +++ b/semver/versions_test.go @@ -16,7 +16,15 @@ limitations under the License. package semver -import "testing" +import ( + "os" + "testing" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/release-tools/since/vcs" +) func TestGetNextVersion(t *testing.T) { type args struct { @@ -75,6 +83,48 @@ func TestGetNextVersion(t *testing.T) { } } +// TestGetCurrentVersion verifies that a "v" prefixed tag is reported with the +// prefix stripped and vPrefix set. Note: vcs caches the latest tag in a +// package-level variable that this package cannot reset, so this test performs +// a single repository lookup to avoid cross-test contamination. +func TestGetCurrentVersion(t *testing.T) { + repoDir := t.TempDir() + + repo, err := git.PlainInit(repoDir, false) + if err != nil { + t.Fatal(err) + } + w, err := repo.Worktree() + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(repoDir+"/README.md", []byte("hello"), 0644); err != nil { + t.Fatal(err) + } + if _, err := w.Add("README.md"); err != nil { + t.Fatal(err) + } + sig := &object.Signature{Name: "user", Email: "user@example.com", When: time.Now()} + c, err := w.Commit("feat: first", &git.CommitOptions{Author: sig, Committer: sig}) + if err != nil { + t.Fatal(err) + } + if _, err := repo.CreateTag("v2.3.4", c, nil); err != nil { + t.Fatal(err) + } + + version, vPrefix, err := GetCurrentVersion(repoDir, vcs.TagOrderSemver) + if err != nil { + t.Fatalf("GetCurrentVersion() error = %v", err) + } + if version != "2.3.4" { + t.Errorf("GetCurrentVersion() version = %q, want %q", version, "2.3.4") + } + if !vPrefix { + t.Errorf("GetCurrentVersion() vPrefix = %v, want true", vPrefix) + } +} + func TestDetermineChangeType(t *testing.T) { type args struct { types []string diff --git a/vcs/operations_test.go b/vcs/operations_test.go index fd6781e..9328f52 100644 --- a/vcs/operations_test.go +++ b/vcs/operations_test.go @@ -49,6 +49,29 @@ func TestCheckBranch_wrongBranch(t *testing.T) { } } +func TestCheckBranch_matchingBranch(t *testing.T) { + repoDir := createTestRepo(t) + + // resolve the repo's actual current branch so the test does not depend on + // whether go-git initialises HEAD as "master" or "main" + branch, err := getCurrentBranch(repoDir) + if err != nil { + t.Fatalf("getCurrentBranch() error = %v", err) + } + + config := cfg.SinceConfig{RequireBranch: branch} + if err := CheckBranch(repoDir, config); err != nil { + t.Errorf("CheckBranch() on required branch error = %v", err) + } +} + +func TestCheckBranch_invalidRepo(t *testing.T) { + config := cfg.SinceConfig{RequireBranch: "main"} + if err := CheckBranch(t.TempDir(), config); err == nil { + t.Error("CheckBranch() expected error for invalid repo") + } +} + func TestCommitChangelog(t *testing.T) { repoDir := createTestRepo(t)