feat(releaser): initial release v0.4.0
Complete GitFlow release automation tool for Conventional Commits workflows: - Core pipeline: branch parsing, tag discovery, commit analysis, version bump - Maven pom.xml read/write, git commit/tag, HTTPS push with token auth - GitLab release creation via API with auto-generated release notes - Configurable via .releaser.yml (tag_prefix, branch_pattern, commit_message, pom_path, gitlab) - CLI flags: --dry-run, --no-push, --no-commit, --tag-only, --branch, --pom, --tag-prefix, --branch-pattern - Dockerfile (multi-stage Alpine), .releaser.gitlab-ci.yml reusable template - Gitea CI (vet + staticcheck + test + build) and release (5-platform cross-compilation) workflows - Taskfile with build/test/cov/lint/fuzz/ci/docker tasks - 96% test coverage with real in-memory git repos and fuzz tests for all parsers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// FuzzParse verifies that the parser never panics and always returns a valid Type.
|
||||
func FuzzParse(f *testing.F) {
|
||||
// Seed corpus: representative conventional commit messages
|
||||
seeds := []string{
|
||||
"feat: add OAuth2 login",
|
||||
"fix: correct null pointer",
|
||||
"feat!: remove legacy API",
|
||||
"feat(auth): add login\n\nBREAKING CHANGE: old endpoint removed",
|
||||
"chore: update deps",
|
||||
"Feat:missing space",
|
||||
"FIX(scope)!: mixed case breaking",
|
||||
"",
|
||||
"\x00",
|
||||
"BREAKING CHANGE: bare footer",
|
||||
"not a conventional commit at all",
|
||||
"feat: \n\n body only",
|
||||
strings.Repeat("a", 4096),
|
||||
}
|
||||
for _, s := range seeds {
|
||||
f.Add(s)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, msg string) {
|
||||
result := Parse(msg)
|
||||
if result < TypeNone || result > TypeBreaking {
|
||||
t.Errorf("Parse(%q) returned out-of-range type %d", msg, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTypeString(t *testing.T) {
|
||||
cases := []struct {
|
||||
t Type
|
||||
want string
|
||||
}{
|
||||
{TypeNone, "none"},
|
||||
{TypeFix, "fix"},
|
||||
{TypeFeat, "feat"},
|
||||
{TypeBreaking, "breaking"},
|
||||
{Type(99), "none"}, // unknown → default
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := c.t.String(); got != c.want {
|
||||
t.Errorf("Type(%d).String() = %q, want %q", c.t, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
msg string
|
||||
want Type
|
||||
}{
|
||||
// standard cases
|
||||
{"feat: add login page", TypeFeat},
|
||||
{"fix: correct null pointer", TypeFix},
|
||||
{"chore: update deps", TypeNone},
|
||||
{"docs: update README", TypeNone},
|
||||
|
||||
// non-strict: case-insensitive
|
||||
{"Feat: add login page", TypeFeat},
|
||||
{"FIX: correct null pointer", TypeFix},
|
||||
{"FEAT: add login", TypeFeat},
|
||||
|
||||
// non-strict: missing space after colon
|
||||
{"feat:add login", TypeFeat},
|
||||
{"fix:correct null pointer", TypeFix},
|
||||
|
||||
// with scope
|
||||
{"feat(auth): add OAuth2", TypeFeat},
|
||||
{"fix(db): prevent deadlock", TypeFix},
|
||||
|
||||
// breaking change via !
|
||||
{"feat!: remove legacy API", TypeBreaking},
|
||||
{"fix!: change response format", TypeBreaking},
|
||||
{"feat(api)!: rename endpoint", TypeBreaking},
|
||||
|
||||
// breaking change via footer
|
||||
{"feat: new API\n\nBREAKING CHANGE: old API removed", TypeBreaking},
|
||||
{"refactor: cleanup\n\nBREAKING CHANGE: interface changed", TypeBreaking},
|
||||
|
||||
// unparseable — silently ignored
|
||||
{"WIP", TypeNone},
|
||||
{"Merge branch 'main' into release/1.2", TypeNone},
|
||||
{"", TypeNone},
|
||||
{"fix a bug without colon", TypeNone},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := Parse(c.msg)
|
||||
if got != c.want {
|
||||
t.Errorf("Parse(%q) = %s, want %s", c.msg, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user