Files
semver-generator/cmd/utils/version_test.go
T
lukaszraczylo dfeb03b8bb fix(semver): skip non-semver tags when picking latest existing tag
Rolling tags like 'v1' (and any other tag that doesn't parse as x.y.z
after prefix stripping) used to enter the latest-tag candidate set in
CalculateSemver. When such a tag shared a commit with a real semver tag
(e.g. v1 and v1.16.3 both pointing at the same release commit), go-git's
alphabetical tag iteration made 'v1' win, ParseExistingSemver bailed out
because '1' has only one component, and the calculator silently reset
the baseline to 0.0.0 — producing nonsense like 0.0.5 on this branch.

ListExistingTags now filters tags through a new IsParseableSemverTag
helper before recording them, so non-semver tags never participate in
latest-tag selection. The behavior change is invisible for repos that
only use proper vX.Y.Z tags, and it's covered by a new test table.
2026-05-22 00:39:59 +01:00

281 lines
6.2 KiB
Go

package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFormatSemver(t *testing.T) {
tests := []struct {
name string
semver SemVer
want string
}{
{
name: "Basic version",
semver: SemVer{
Major: 1,
Minor: 2,
Patch: 3,
},
want: "1.2.3",
},
{
name: "With release candidate",
semver: SemVer{
Major: 2,
Minor: 0,
Patch: 1,
Release: 5,
EnableReleaseCandidate: true,
},
want: "2.0.1-rc.5",
},
{
name: "With release candidate disabled",
semver: SemVer{
Major: 3,
Minor: 1,
Patch: 0,
Release: 2,
EnableReleaseCandidate: false,
},
want: "3.1.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FormatSemver(tt.semver)
assert.Equal(t, tt.want, got)
})
}
}
func TestParseExistingSemver(t *testing.T) {
// Initialize logger for tests
InitLogger(false)
tests := []struct {
name string
tagName string
currentSemver SemVer
prefixes []string
want SemVer
}{
{
name: "Standard semver",
tagName: "1.2.3",
currentSemver: SemVer{},
prefixes: []string{},
want: SemVer{
Major: 1,
Minor: 2,
Patch: 3,
},
},
{
name: "With v prefix configured",
tagName: "v2.3.4",
currentSemver: SemVer{},
prefixes: []string{"v"},
want: SemVer{
Major: 2,
Minor: 3,
Patch: 4,
},
},
{
name: "With app- prefix configured",
tagName: "app-1.2.3",
currentSemver: SemVer{},
prefixes: []string{"app-", "infra-"},
want: SemVer{
Major: 1,
Minor: 2,
Patch: 3,
},
},
{
name: "With prefix but not in config - should still parse numbers",
tagName: "v2.3.4",
currentSemver: SemVer{},
prefixes: []string{}, // v not in prefixes
want: SemVer{
Major: 2,
Minor: 3,
Patch: 4,
},
},
{
name: "With release candidate",
tagName: "3.4.5-rc.2",
currentSemver: SemVer{},
prefixes: []string{},
want: SemVer{
Major: 3,
Minor: 4,
Patch: 5,
Release: 2,
EnableReleaseCandidate: true,
},
},
{
name: "With prefix and release candidate",
tagName: "app-1.0.0-rc.3",
currentSemver: SemVer{},
prefixes: []string{"app-"},
want: SemVer{
Major: 1,
Minor: 0,
Patch: 0,
Release: 3,
EnableReleaseCandidate: true,
},
},
{
name: "Prefixed tag without RC should NOT be RC",
tagName: "app-0.0.16",
currentSemver: SemVer{},
prefixes: []string{"app-"},
want: SemVer{
Major: 0,
Minor: 0,
Patch: 16,
EnableReleaseCandidate: false,
},
},
{
name: "Invalid format",
tagName: "not-a-semver",
currentSemver: SemVer{
Major: 1,
Minor: 1,
Patch: 1,
},
prefixes: []string{},
want: SemVer{
Major: 1,
Minor: 1,
Patch: 1,
},
},
{
name: "Incomplete format",
tagName: "1.2",
currentSemver: SemVer{
Major: 5,
Minor: 5,
Patch: 5,
},
prefixes: []string{},
want: SemVer{
Major: 5,
Minor: 5,
Patch: 5,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ParseExistingSemver(tt.tagName, tt.currentSemver, tt.prefixes)
assert.Equal(t, tt.want.Major, got.Major, "Major version mismatch")
assert.Equal(t, tt.want.Minor, got.Minor, "Minor version mismatch")
assert.Equal(t, tt.want.Patch, got.Patch, "Patch version mismatch")
assert.Equal(t, tt.want.Release, got.Release, "Release version mismatch")
assert.Equal(t, tt.want.EnableReleaseCandidate, got.EnableReleaseCandidate, "EnableReleaseCandidate mismatch")
})
}
}
func TestIsParseableSemverTag(t *testing.T) {
InitLogger(false)
tests := []struct {
name string
tag string
prefixes []string
want bool
}{
{name: "plain x.y.z", tag: "1.2.3", want: true},
{name: "v prefix", tag: "v1.16.5", want: true},
{name: "v prefix with rc", tag: "v2.0.0-rc.3", want: true},
{name: "configured app- prefix", tag: "app-1.2.3", prefixes: []string{"app-"}, want: true},
{name: "rolling v1 tag", tag: "v1", want: false},
{name: "rolling latest tag", tag: "latest", want: false},
{name: "two-component", tag: "1.2", want: false},
{name: "empty", tag: "", want: false},
{name: "non-numeric major", tag: "vX.Y.Z", want: false},
{name: "just text", tag: "release-day", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, IsParseableSemverTag(tt.tag, tt.prefixes))
})
}
}
func TestCheckMatches(t *testing.T) {
// Initialize logger for tests
InitLogger(false)
// Mock the fuzzy find function for testing
originalFuzzyFind := FuzzyFind
defer func() { FuzzyFind = originalFuzzyFind }()
FuzzyFind = func(needle string, haystack []string) []string {
// Simple mock implementation for testing
for _, h := range haystack {
if h == needle {
return []string{h}
}
}
return nil
}
tests := []struct {
name string
content []string
targets []string
blacklist []string
want bool
}{
{
name: "Simple match",
content: []string{"update", "dependencies"},
targets: []string{"update", "fix"},
want: true,
},
{
name: "No match",
content: []string{"chore", "dependencies"},
targets: []string{"update", "fix"},
want: false,
},
{
name: "Match but blacklisted",
content: []string{"update", "dependencies", "skip-ci"},
targets: []string{"update", "fix"},
blacklist: []string{"skip-ci"},
want: false,
},
{
name: "Match with empty blacklist",
content: []string{"update", "dependencies"},
targets: []string{"update", "fix"},
blacklist: []string{},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CheckMatches(tt.content, tt.targets, tt.blacklist)
assert.Equal(t, tt.want, got)
})
}
}