Compare commits

...

1 Commits

Author SHA1 Message Date
lukaszraczylo 3e0a7239c4 Fix ignoring strict.force and strict.commit. 2025-12-15 13:37:07 +00:00
12 changed files with 63 additions and 266 deletions
-16
View File
@@ -1,16 +0,0 @@
package cmd
import (
"github.com/lukaszraczylo/semver-generator/cmd/utils"
)
// These functions are now in the utils package
// They are kept here as stubs for backward compatibility
func updatePackage() bool {
return utils.UpdatePackage()
}
func checkLatestRelease() (string, bool) {
return utils.CheckLatestRelease()
}
-52
View File
@@ -1,52 +0,0 @@
package cmd
import (
"testing"
"github.com/lukaszraczylo/semver-generator/cmd/utils"
)
func Test_checkLatestRelease(t *testing.T) {
utils.InitLogger(true)
tests := []struct {
name string
want string
want1 bool
}{
{
name: "Check latest release",
want1: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, got1 := checkLatestRelease()
if got1 != tt.want1 {
t.Errorf("checkLatestRelease() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func Test_updatePackage(t *testing.T) {
utils.InitLogger(true)
if testing.Short() {
t.Skip("Skipping test in short / CI mode")
}
tests := []struct {
name string
want bool
}{
{
name: "Run autoupdater",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := updatePackage(); got != tt.want {
t.Errorf("updatePackage() = %v, want %v", got, tt.want)
}
})
}
}
+3 -3
View File
@@ -30,13 +30,13 @@ func TestExecute(t *testing.T) {
Short: "Test command", Short: "Test command",
Run: func(cmd *cobra.Command, args []string) {}, Run: func(cmd *cobra.Command, args []string) {},
} }
// Add all the required flags to the test command // Add all the required flags to the test command
testCmd.Flags().Bool("version", false, "Print version information") testCmd.Flags().Bool("version", false, "Print version information")
testCmd.Flags().String("repository", "test-repo", "Repository URL") testCmd.Flags().String("repository", "test-repo", "Repository URL")
testCmd.Flags().String("branch", "test-branch", "Repository branch") testCmd.Flags().String("branch", "test-branch", "Repository branch")
testCmd.Flags().String("config", "test-config", "Config file path") testCmd.Flags().String("config", "test-config", "Config file path")
rootCmd = testCmd rootCmd = testCmd
// Execute should not panic // Execute should not panic
@@ -82,4 +82,4 @@ func TestSetupCobra(t *testing.T) {
assertions.Equal(t, "test-branch", testRepo.RepositoryBranch, "Repository branch should be set") assertions.Equal(t, "test-branch", testRepo.RepositoryBranch, "Repository branch should be set")
assertions.Equal(t, "test-config", testRepo.LocalConfigFile, "Config file should be set") assertions.Equal(t, "test-config", testRepo.LocalConfigFile, "Config file should be set")
assertions.True(t, testRepo.UseLocal, "UseLocal should be set to true") assertions.True(t, testRepo.UseLocal, "UseLocal should be set to true")
} }
+1 -1
View File
@@ -198,4 +198,4 @@ wording:
// Test reading a non-existent config // Test reading a non-existent config
_, err = ReadConfig("non-existent-file.yaml") _, err = ReadConfig("non-existent-file.yaml")
assert.Error(t, err) assert.Error(t, err)
} }
+9 -2
View File
@@ -135,18 +135,25 @@ func ListCommits(repo *GitRepository) ([]CommitDetails, error) {
Debug("Listing commits", map[string]interface{}{"commits": tmpResults}) Debug("Listing commits", map[string]interface{}{"commits": tmpResults})
// Filter commits starting from the specified commit if provided // Filter commits starting after the specified commit if provided
if repo.StartCommit != "" { if repo.StartCommit != "" {
found := false
for commitId, cmt := range tmpResults { for commitId, cmt := range tmpResults {
if cmt.Hash == repo.StartCommit { if cmt.Hash == repo.StartCommit {
Debug("Found commit match", map[string]interface{}{ Debug("Found commit match", map[string]interface{}{
"commit": cmt.Hash, "commit": cmt.Hash,
"index": commitId, "index": commitId,
}) })
repo.Commits = tmpResults[commitId:] // Start from the commit AFTER the specified one
repo.Commits = tmpResults[commitId+1:]
found = true
break break
} }
} }
if !found {
// If specified commit not found, use all commits
repo.Commits = tmpResults
}
} else { } else {
repo.Commits = tmpResults repo.Commits = tmpResults
} }
+13 -13
View File
@@ -64,7 +64,7 @@ func TestListCommits(t *testing.T) {
t.Run("Test commit filtering logic", func(t *testing.T) { t.Run("Test commit filtering logic", func(t *testing.T) {
// Create a test repository with predefined commits // Create a test repository with predefined commits
repo := &GitRepository{} repo := &GitRepository{}
// Manually populate the commits for testing // Manually populate the commits for testing
repo.Commits = []CommitDetails{ repo.Commits = []CommitDetails{
{ {
@@ -83,7 +83,7 @@ func TestListCommits(t *testing.T) {
// Test with StartCommit specified // Test with StartCommit specified
repo.StartCommit = "def456" repo.StartCommit = "def456"
// Instead of calling ListCommits which would try to use the nil Handler, // Instead of calling ListCommits which would try to use the nil Handler,
// we'll just test the filtering logic directly // we'll just test the filtering logic directly
if repo.StartCommit != "" { if repo.StartCommit != "" {
@@ -94,19 +94,19 @@ func TestListCommits(t *testing.T) {
} }
} }
} }
// Verify the filtering worked correctly // Verify the filtering worked correctly
assert.Len(t, repo.Commits, 1, "Should filter commits starting from specified hash") assert.Len(t, repo.Commits, 1, "Should filter commits starting from specified hash")
assert.Equal(t, "def456", repo.Commits[0].Hash, "Commit hash should match") assert.Equal(t, "def456", repo.Commits[0].Hash, "Commit hash should match")
}) })
t.Run("Test with nil Handler", func(t *testing.T) { t.Run("Test with nil Handler", func(t *testing.T) {
// Create a test repository with nil Handler // Create a test repository with nil Handler
repo := &GitRepository{} repo := &GitRepository{}
// Now we can safely call ListCommits since we've added a nil check // Now we can safely call ListCommits since we've added a nil check
commits, err := ListCommits(repo) commits, err := ListCommits(repo)
// Verify the function returns without error // Verify the function returns without error
assert.NoError(t, err, "Should not error with nil Handler") assert.NoError(t, err, "Should not error with nil Handler")
assert.Empty(t, commits, "Should return empty commits with nil Handler") assert.Empty(t, commits, "Should return empty commits with nil Handler")
@@ -120,10 +120,10 @@ func TestListExistingTags(t *testing.T) {
t.Run("Test tag processing", func(t *testing.T) { t.Run("Test tag processing", func(t *testing.T) {
// Create a test repository // Create a test repository
repo := &GitRepository{} repo := &GitRepository{}
// Since we can't test the actual git operations, we'll test the function's behavior // Since we can't test the actual git operations, we'll test the function's behavior
// by manually setting up the repository state // by manually setting up the repository state
// Manually add tags to verify they're processed correctly // Manually add tags to verify they're processed correctly
repo.Tags = []TagDetails{ repo.Tags = []TagDetails{
{ {
@@ -131,20 +131,20 @@ func TestListExistingTags(t *testing.T) {
Hash: "abc123", Hash: "abc123",
}, },
} }
assert.Len(t, repo.Tags, 1, "Should have 1 tag") assert.Len(t, repo.Tags, 1, "Should have 1 tag")
assert.Equal(t, "v1.0.0", repo.Tags[0].Name, "Tag name should match") assert.Equal(t, "v1.0.0", repo.Tags[0].Name, "Tag name should match")
assert.Equal(t, "abc123", repo.Tags[0].Hash, "Tag hash should match") assert.Equal(t, "abc123", repo.Tags[0].Hash, "Tag hash should match")
}) })
t.Run("Test with nil Handler", func(t *testing.T) { t.Run("Test with nil Handler", func(t *testing.T) {
// Create a test repository with nil Handler // Create a test repository with nil Handler
repo := &GitRepository{} repo := &GitRepository{}
// Now we can safely call ListExistingTags since we've added a nil check // Now we can safely call ListExistingTags since we've added a nil check
ListExistingTags(repo) ListExistingTags(repo)
// Verify no tags were added // Verify no tags were added
assert.Empty(t, repo.Tags, "Should have no tags after calling with nil Handler") assert.Empty(t, repo.Tags, "Should have no tags after calling with nil Handler")
}) })
} }
-78
View File
@@ -34,14 +34,6 @@ type ReleaseAsset struct {
BrowserDownloadURL string `json:"browser_download_url"` BrowserDownloadURL string `json:"browser_download_url"`
} }
// UpdateInfo contains information about an available update
type UpdateInfo struct {
CurrentVersion string
LatestVersion string
ReleaseURL string
DownloadURL string
}
// httpClient is the HTTP client used for requests (allows mocking in tests) // httpClient is the HTTP client used for requests (allows mocking in tests)
var httpClient = &http.Client{ var httpClient = &http.Client{
Timeout: requestTimeout, Timeout: requestTimeout,
@@ -60,30 +52,6 @@ func CheckLatestRelease() (string, bool) {
return version, true return version, true
} }
// CheckForUpdate checks if a newer version is available
// Returns UpdateInfo if an update is available, nil otherwise
func CheckForUpdate(currentVersion string) *UpdateInfo {
release, err := fetchLatestRelease(context.Background())
if err != nil {
return nil
}
latestVersion := normalizeVersion(release.TagName)
current := normalizeVersion(currentVersion)
if isNewerVersion(latestVersion, current) {
downloadURL := findBinaryAsset(release.Assets)
return &UpdateInfo{
CurrentVersion: current,
LatestVersion: latestVersion,
ReleaseURL: release.HTMLURL,
DownloadURL: downloadURL,
}
}
return nil
}
// UpdatePackage downloads and installs the latest version // UpdatePackage downloads and installs the latest version
func UpdatePackage() bool { func UpdatePackage() bool {
Info("Checking for updates", nil) Info("Checking for updates", nil)
@@ -389,49 +357,3 @@ func normalizeVersion(v string) string {
v = strings.TrimPrefix(v, "V") v = strings.TrimPrefix(v, "V")
return v return v
} }
// isNewerVersion compares two semver-like versions
// Returns true if latest is newer than current
func isNewerVersion(latest, current string) bool {
latestParts := parseVersionParts(latest)
currentParts := parseVersionParts(current)
for i := 0; i < len(latestParts) && i < len(currentParts); i++ {
if latestParts[i] > currentParts[i] {
return true
}
if latestParts[i] < currentParts[i] {
return false
}
}
return len(latestParts) > len(currentParts)
}
// parseVersionParts splits a version string into numeric parts
func parseVersionParts(v string) []int {
// Remove any suffix like -beta, -rc1, etc.
if idx := strings.IndexAny(v, "-+"); idx != -1 {
v = v[:idx]
}
parts := strings.Split(v, ".")
result := make([]int, 0, len(parts))
for _, p := range parts {
var num int
if _, err := fmt.Sscanf(p, "%d", &num); err != nil {
// If parsing fails, use 0 for this part
num = 0
}
result = append(result, num)
}
return result
}
// FormatUpdateMessage formats a user-friendly update notification
func (u *UpdateInfo) FormatUpdateMessage() string {
return fmt.Sprintf("New version available: %s (current: %s) - %s",
u.LatestVersion, u.CurrentVersion, u.ReleaseURL)
}
-80
View File
@@ -30,57 +30,6 @@ func TestNormalizeVersion(t *testing.T) {
} }
} }
func TestParseVersionParts(t *testing.T) {
tests := []struct {
input string
expected []int
}{
{"1.0.0", []int{1, 0, 0}},
{"2.1.3", []int{2, 1, 3}},
{"1.0", []int{1, 0}},
{"10.20.30", []int{10, 20, 30}},
{"1.0.0-beta", []int{1, 0, 0}},
{"1.0.0-rc1", []int{1, 0, 0}},
{"1.0.0+build123", []int{1, 0, 0}},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := parseVersionParts(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsNewerVersion(t *testing.T) {
tests := []struct {
name string
latest string
current string
expected bool
}{
{"major version bump", "2.0.0", "1.0.0", true},
{"minor version bump", "1.1.0", "1.0.0", true},
{"patch version bump", "1.0.1", "1.0.0", true},
{"same version", "1.0.0", "1.0.0", false},
{"current is newer major", "1.0.0", "2.0.0", false},
{"current is newer minor", "1.0.0", "1.1.0", false},
{"current is newer patch", "1.0.0", "1.0.1", false},
{"multi-digit versions", "1.10.0", "1.9.0", true},
{"longer version is newer", "1.0.1", "1.0", true},
{"shorter version is older", "1.0", "1.0.1", false},
{"complex comparison", "2.1.3", "2.1.2", true},
{"real world example", "0.2.0", "0.1.0", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isNewerVersion(tt.latest, tt.current)
assert.Equal(t, tt.expected, result)
})
}
}
func TestFindBinaryAsset(t *testing.T) { func TestFindBinaryAsset(t *testing.T) {
assets := []ReleaseAsset{ assets := []ReleaseAsset{
{Name: "semver-gen-1.0.0-linux-amd64.tar.gz", BrowserDownloadURL: "https://example.com/linux-amd64.tar.gz"}, {Name: "semver-gen-1.0.0-linux-amd64.tar.gz", BrowserDownloadURL: "https://example.com/linux-amd64.tar.gz"},
@@ -102,19 +51,6 @@ func TestFindBinaryAssetEmpty(t *testing.T) {
assert.Empty(t, url, "Should return empty string when no assets") assert.Empty(t, url, "Should return empty string when no assets")
} }
func TestUpdateInfo_FormatUpdateMessage(t *testing.T) {
info := &UpdateInfo{
CurrentVersion: "1.0.0",
LatestVersion: "2.0.0",
ReleaseURL: "https://github.com/lukaszraczylo/semver-generator/releases/tag/v2.0.0",
}
msg := info.FormatUpdateMessage()
assert.Contains(t, msg, "2.0.0")
assert.Contains(t, msg, "1.0.0")
assert.Contains(t, msg, "https://github.com/lukaszraczylo/semver-generator/releases/tag/v2.0.0")
}
func TestCheckLatestRelease(t *testing.T) { func TestCheckLatestRelease(t *testing.T) {
// Initialize logger // Initialize logger
InitLogger(false) InitLogger(false)
@@ -141,22 +77,6 @@ func TestCheckLatestRelease(t *testing.T) {
} }
} }
func TestCheckForUpdate(t *testing.T) {
InitLogger(false)
// Test with a very old version - should show update available if network works
info := CheckForUpdate("0.0.1")
// This will either return update info or nil depending on network
if info != nil {
assert.NotEmpty(t, info.LatestVersion)
assert.Equal(t, "0.0.1", info.CurrentVersion)
}
// Test with a very new version - should not show update
info = CheckForUpdate("999.999.999")
assert.Nil(t, info, "Should not show update for future version")
}
func TestFetchLatestReleaseError(t *testing.T) { func TestFetchLatestReleaseError(t *testing.T) {
InitLogger(false) InitLogger(false)
+1 -1
View File
@@ -56,4 +56,4 @@ func Critical(message string, pairs map[string]interface{}) {
Pairs: pairs, Pairs: pairs,
}) })
} }
} }
+9 -8
View File
@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestInitLogger(t *testing.T) { func TestInitLogger(t *testing.T) {
// Test with debug mode enabled // Test with debug mode enabled
logger := InitLogger(true) logger := InitLogger(true)
@@ -25,10 +26,10 @@ func TestLoggingFunctions(t *testing.T) {
Debug("Debug message", map[string]interface{}{"key": "value"}) Debug("Debug message", map[string]interface{}{"key": "value"})
Info("Info message", map[string]interface{}{"key": "value"}) Info("Info message", map[string]interface{}{"key": "value"})
Error("Error message", map[string]interface{}{"key": "value"}) Error("Error message", map[string]interface{}{"key": "value"})
// Skip testing Critical as it might call os.Exit // Skip testing Critical as it might call os.Exit
// Critical("Critical message", map[string]interface{}{"key": "value"}) // Critical("Critical message", map[string]interface{}{"key": "value"})
// Test passes if we get here without panicking // Test passes if we get here without panicking
assert.True(t, true) assert.True(t, true)
} }
@@ -43,10 +44,10 @@ func TestLoggingWithNilLogger(t *testing.T) {
Debug("Debug message", map[string]interface{}{"key": "value"}) Debug("Debug message", map[string]interface{}{"key": "value"})
Info("Info message", map[string]interface{}{"key": "value"}) Info("Info message", map[string]interface{}{"key": "value"})
Error("Error message", map[string]interface{}{"key": "value"}) Error("Error message", map[string]interface{}{"key": "value"})
// Skip testing Critical as it might call os.Exit // Skip testing Critical as it might call os.Exit
// Critical("Critical message", map[string]interface{}{"key": "value"}) // Critical("Critical message", map[string]interface{}{"key": "value"})
// Test passes if we get here without panicking // Test passes if we get here without panicking
assert.True(t, true) assert.True(t, true)
} }
@@ -56,15 +57,15 @@ func TestCriticalNilLogger(t *testing.T) {
// Save original logger and restore after test // Save original logger and restore after test
originalLogger := Logger originalLogger := Logger
defer func() { Logger = originalLogger }() defer func() { Logger = originalLogger }()
// Set logger to nil // Set logger to nil
Logger = nil Logger = nil
// This should not panic // This should not panic
Critical("Critical message", map[string]interface{}{"key": "value"}) Critical("Critical message", map[string]interface{}{"key": "value"})
// Test passes if we get here without panicking // Test passes if we get here without panicking
assert.True(t, true) assert.True(t, true)
} }
// Note: We don't test Critical with an actual logger because it calls os.Exit // Note: We don't test Critical with an actual logger because it calls os.Exit
+26 -11
View File
@@ -16,22 +16,37 @@ func CalculateSemver(
tagPrefixes []string, tagPrefixes []string,
) SemVer { ) SemVer {
semver := initialSemver semver := initialSemver
startIndex := 0
for _, commit := range commits { // If respecting existing tags, find the latest tagged commit and start from there
// Check for existing tags if enabled if respectExisting && len(tags) > 0 {
if respectExisting { latestTagIndex := -1
for _, tagHash := range tags { var latestTagName string
if commit.Hash == tagHash.Hash {
Debug("Found existing tag", map[string]interface{}{ // Find the latest tagged commit (highest index since commits are oldest-first)
"tag": tagHash.Name, for i, commit := range commits {
"commit": strings.TrimSuffix(commit.Message, "\n"), for _, tag := range tags {
}) if commit.Hash == tag.Hash {
semver = ParseExistingSemver(tagHash.Name, semver, tagPrefixes) if i > latestTagIndex {
continue latestTagIndex = i
latestTagName = tag.Name
}
} }
} }
} }
// If we found a tagged commit, use its version and start processing after it
if latestTagIndex >= 0 {
Debug("Found latest existing tag", map[string]interface{}{
"tag": latestTagName,
"commit": strings.TrimSuffix(commits[latestTagIndex].Message, "\n"),
})
semver = ParseExistingSemver(latestTagName, semver, tagPrefixes)
startIndex = latestTagIndex + 1
}
}
for _, commit := range commits[startIndex:] {
// In non-strict mode, increment patch by default // In non-strict mode, increment patch by default
if !strictMode { if !strictMode {
semver.Patch++ semver.Patch++
+1 -1
View File
@@ -30,4 +30,4 @@ func TestMain(t *testing.T) {
// Verify that the version was set correctly // Verify that the version was set correctly
assert.Equal(t, "test-version", cmd.PKG_VERSION, "PKG_VERSION should be set correctly") assert.Equal(t, "test-version", cmd.PKG_VERSION, "PKG_VERSION should be set correctly")
} }