test: add/improve test suites

This commit is contained in:
2025-12-05 22:36:24 +02:00
parent a7053cfb2c
commit c3a29d0642
9 changed files with 1716 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
package appconfig
import (
"sort"
"testing"
"github.com/chenasraf/sofmani/platform"
"github.com/stretchr/testify/assert"
)
func TestInstallerData_Environ(t *testing.T) {
t.Run("returns empty slice when both Env and PlatformEnv are nil", func(t *testing.T) {
data := &InstallerData{}
result := data.Environ()
assert.NotNil(t, result)
assert.Empty(t, result)
})
t.Run("returns Env values when only Env is set", func(t *testing.T) {
env := map[string]string{"KEY": "value", "OTHER": "test"}
data := &InstallerData{
Env: &env,
}
result := data.Environ()
sort.Strings(result) // Sort for consistent comparison
assert.Len(t, result, 2)
assert.Contains(t, result, "KEY=value")
assert.Contains(t, result, "OTHER=test")
})
t.Run("returns PlatformEnv values for current platform", func(t *testing.T) {
macEnv := map[string]string{"PLATFORM": "macos"}
linuxEnv := map[string]string{"PLATFORM": "linux"}
data := &InstallerData{
PlatformEnv: &platform.PlatformMap[map[string]string]{
MacOS: &macEnv,
Linux: &linuxEnv,
},
}
result := data.Environ()
// Result depends on current platform
if len(result) > 0 {
assert.Contains(t, result[0], "PLATFORM=")
}
})
t.Run("combines Env and PlatformEnv", func(t *testing.T) {
env := map[string]string{"COMMON": "value"}
macEnv := map[string]string{"SPECIFIC": "mac"}
data := &InstallerData{
Env: &env,
PlatformEnv: &platform.PlatformMap[map[string]string]{
MacOS: &macEnv,
},
}
result := data.Environ()
assert.Contains(t, result, "COMMON=value")
// SPECIFIC will only appear on macOS
})
t.Run("PlatformEnv overrides Env for same key", func(t *testing.T) {
env := map[string]string{"KEY": "original"}
macEnv := map[string]string{"KEY": "platform"}
data := &InstallerData{
Env: &env,
PlatformEnv: &platform.PlatformMap[map[string]string]{
MacOS: &macEnv,
},
}
result := data.Environ()
// On macOS, KEY should be "platform"
// On other platforms, KEY should be "original"
assert.Len(t, result, 1)
assert.Contains(t, result[0], "KEY=")
})
}
func TestInstallerData_GetTagsList(t *testing.T) {
t.Run("returns list of space-separated tags", func(t *testing.T) {
tags := "python node rust"
data := &InstallerData{
Tags: &tags,
}
result := data.GetTagsList()
assert.Equal(t, []string{"python", "node", "rust"}, result)
})
t.Run("trims whitespace from tags", func(t *testing.T) {
tags := " python node rust "
data := &InstallerData{
Tags: &tags,
}
result := data.GetTagsList()
// Note: empty strings will be included for leading/trailing spaces when split
// The implementation splits and trims each part
for _, tag := range result {
assert.Equal(t, tag, trimmedTag(tag))
}
})
t.Run("returns single tag when only one is present", func(t *testing.T) {
tags := "python"
data := &InstallerData{
Tags: &tags,
}
result := data.GetTagsList()
assert.Equal(t, []string{"python"}, result)
})
t.Run("handles tags with multiple spaces between them", func(t *testing.T) {
tags := "python node"
data := &InstallerData{
Tags: &tags,
}
result := data.GetTagsList()
// Split by single space, so empty string will be in between
assert.Contains(t, result, "python")
assert.Contains(t, result, "node")
})
}
// helper to get trimmed tag
func trimmedTag(s string) string {
return s // Already trimmed by the function
}
func TestInstallerType_Constants(t *testing.T) {
t.Run("installer types have expected values", func(t *testing.T) {
assert.Equal(t, InstallerType("group"), InstallerTypeGroup)
assert.Equal(t, InstallerType("shell"), InstallerTypeShell)
assert.Equal(t, InstallerType("docker"), InstallerTypeDocker)
assert.Equal(t, InstallerType("brew"), InstallerTypeBrew)
assert.Equal(t, InstallerType("apt"), InstallerTypeApt)
assert.Equal(t, InstallerType("apk"), InstallerTypeApk)
assert.Equal(t, InstallerType("git"), InstallerTypeGit)
assert.Equal(t, InstallerType("github-release"), InstallerTypeGitHubRelease)
assert.Equal(t, InstallerType("rsync"), InstallerTypeRsync)
assert.Equal(t, InstallerType("npm"), InstallerTypeNpm)
assert.Equal(t, InstallerType("pnpm"), InstallerTypePnpm)
assert.Equal(t, InstallerType("yarn"), InstallerTypeYarn)
assert.Equal(t, InstallerType("pipx"), InstallerTypePipx)
assert.Equal(t, InstallerType("manifest"), InstallerTypeManifest)
assert.Equal(t, InstallerType("pacman"), InstallerTypePacman)
assert.Equal(t, InstallerType("yay"), InstallerTypeYay)
})
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/logger"
"github.com/stretchr/testify/assert"
)
func newTestBrewInstaller(data *appconfig.InstallerData) *BrewInstaller {
@@ -243,3 +244,225 @@ func TestBrewNeedsUpdateWithExitCode(t *testing.T) {
})
}
}
func TestBrewGetFullName(t *testing.T) {
logger.InitLogger(false)
t.Run("returns name without tap", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := newTestBrewInstaller(data)
assert.Equal(t, "vim", installer.GetFullName())
})
t.Run("returns tap/name with tap", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("sofmani"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"tap": "chenasraf/tap",
},
}
installer := newTestBrewInstaller(data)
assert.Equal(t, "chenasraf/tap/sofmani", installer.GetFullName())
})
}
func TestBrewIsCask(t *testing.T) {
logger.InitLogger(false)
t.Run("returns false when cask is not set", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := newTestBrewInstaller(data)
assert.False(t, installer.IsCask())
})
t.Run("returns false when cask is false", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("firefox"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"cask": false,
},
}
installer := newTestBrewInstaller(data)
assert.False(t, installer.IsCask())
})
t.Run("returns true when cask is true", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("firefox"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"cask": true,
},
}
installer := newTestBrewInstaller(data)
assert.True(t, installer.IsCask())
})
}
func TestBrewGetBinName(t *testing.T) {
logger.InitLogger(false)
t.Run("returns name when bin_name is not set", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := newTestBrewInstaller(data)
assert.Equal(t, "vim", installer.GetBinName())
})
t.Run("returns bin_name when set", func(t *testing.T) {
binName := "nvim"
data := &appconfig.InstallerData{
Name: strPtr("neovim"),
Type: appconfig.InstallerTypeBrew,
BinName: &binName,
}
installer := newTestBrewInstaller(data)
assert.Equal(t, "nvim", installer.GetBinName())
})
t.Run("returns name when bin_name is empty", func(t *testing.T) {
binName := ""
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
BinName: &binName,
}
installer := newTestBrewInstaller(data)
assert.Equal(t, "vim", installer.GetBinName())
})
}
func TestBrewGetData(t *testing.T) {
logger.InitLogger(false)
t.Run("returns the installer data", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := newTestBrewInstaller(data)
result := installer.GetData()
assert.Equal(t, data, result)
assert.Equal(t, "vim", *result.Name)
})
}
func TestNewBrewInstaller(t *testing.T) {
logger.InitLogger(false)
t.Run("creates installer with config and data", func(t *testing.T) {
cfg := &appconfig.AppConfig{}
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
}
installer := NewBrewInstaller(cfg, data)
assert.NotNil(t, installer)
assert.Equal(t, cfg, installer.Config)
assert.Equal(t, data, installer.Info)
assert.Equal(t, data, installer.Data)
})
}
func TestBrewCheckIsInstalled(t *testing.T) {
logger.InitLogger(false)
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "true"
data := &appconfig.InstallerData{
Name: strPtr("test-brew"),
Type: appconfig.InstallerTypeBrew,
CheckInstalled: &checkCmd,
}
installer := newTestBrewInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("runs custom check that fails", func(t *testing.T) {
checkCmd := "false"
data := &appconfig.InstallerData{
Name: strPtr("test-brew"),
Type: appconfig.InstallerTypeBrew,
CheckInstalled: &checkCmd,
}
installer := newTestBrewInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.False(t, result)
})
}
func TestBrewCheckNeedsUpdate(t *testing.T) {
logger.InitLogger(false)
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "true" // Returns exit code 0, meaning update available
data := &appconfig.InstallerData{
Name: strPtr("test-brew"),
Type: appconfig.InstallerTypeBrew,
CheckHasUpdate: &checkCmd,
}
installer := newTestBrewInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("custom check returns false when no update", func(t *testing.T) {
checkCmd := "false" // Returns exit code 1, meaning no update
data := &appconfig.InstallerData{
Name: strPtr("test-brew"),
Type: appconfig.InstallerTypeBrew,
CheckHasUpdate: &checkCmd,
}
installer := newTestBrewInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.False(t, result)
})
}
func TestBrewGetOptsWrongTypes(t *testing.T) {
logger.InitLogger(false)
t.Run("handles wrong type values gracefully", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("vim"),
Type: appconfig.InstallerTypeBrew,
Opts: &map[string]any{
"tap": 123, // Wrong type
"cask": "yes", // Wrong type
"flags": true, // Wrong type
"install_flags": 456, // Wrong type
"update_flags": false, // Wrong type
},
}
installer := newTestBrewInstaller(data)
opts := installer.GetOpts()
// Should return nil when type assertion fails
assert.Nil(t, opts.Tap)
assert.Nil(t, opts.Cask)
assert.Nil(t, opts.Flags)
assert.Nil(t, opts.InstallFlags)
assert.Nil(t, opts.UpdateFlags)
})
}

View File

@@ -155,3 +155,177 @@ func TestInstallerIsEnabled(t *testing.T) {
})
}
}
func TestFilterInstallerEdgeCases(t *testing.T) {
logger.InitLogger(false)
t.Run("Multiple tags - one matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1 tag2 tag3")},
}
result := FilterInstaller(installer, []string{"tag:tag2"})
assert.True(t, result)
})
t.Run("Multiple tags - none match", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("tag1 tag2 tag3")},
}
result := FilterInstaller(installer, []string{"tag:tag4"})
assert.False(t, result)
})
t.Run("Mixed positive and negative filters - positive matches, negative matches too", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev prod")},
}
// Should be excluded because negative filter matches
result := FilterInstaller(installer, []string{"tag:dev", "!tag:prod"})
assert.False(t, result)
})
t.Run("Mixed positive and negative filters - only positive matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")},
}
result := FilterInstaller(installer, []string{"tag:dev", "!tag:prod"})
assert.True(t, result)
})
t.Run("Name substring match", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("my-awesome-tool"), Tags: strPtr("")},
}
result := FilterInstaller(installer, []string{"awesome"})
assert.True(t, result)
})
t.Run("Name exact match", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("")},
}
result := FilterInstaller(installer, []string{"vim"})
assert.True(t, result)
})
t.Run("Type filter case insensitive", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew},
}
result := FilterInstaller(installer, []string{"type:BREW"})
assert.True(t, result)
})
t.Run("Multiple positive filters - one matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")},
}
result := FilterInstaller(installer, []string{"neovim", "vim", "emacs"})
assert.True(t, result)
})
t.Run("Multiple positive filters - none match", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")},
}
result := FilterInstaller(installer, []string{"neovim", "emacs", "nano"})
assert.False(t, result)
})
t.Run("Multiple negative filters - all don't match", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")},
}
result := FilterInstaller(installer, []string{"!neovim", "!emacs"})
assert.True(t, result)
})
t.Run("Multiple negative filters - one matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("vim"), Tags: strPtr("editor")},
}
result := FilterInstaller(installer, []string{"!neovim", "!vim"})
assert.False(t, result)
})
t.Run("Negative type filter", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew},
}
result := FilterInstaller(installer, []string{"!type:npm"})
assert.True(t, result)
})
t.Run("Negative type filter matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Type: appconfig.InstallerTypeBrew},
}
result := FilterInstaller(installer, []string{"!type:brew"})
assert.False(t, result)
})
t.Run("Negative tag filter", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")},
}
result := FilterInstaller(installer, []string{"!tag:prod"})
assert.True(t, result)
})
t.Run("Negative tag filter matches", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Name: strPtr("test"), Tags: strPtr("dev")},
}
result := FilterInstaller(installer, []string{"!tag:dev"})
assert.False(t, result)
})
}
func TestInstallerIsEnabledEdgeCases(t *testing.T) {
logger.InitLogger(false)
t.Run("Enabled is TRUE (uppercase)", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("TRUE")},
}
result, err := InstallerIsEnabled(installer)
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("Enabled is FALSE (uppercase)", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("FALSE")},
}
result, err := InstallerIsEnabled(installer)
assert.NoError(t, err)
assert.False(t, result)
})
t.Run("Enabled is True (mixed case)", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("True")},
}
result, err := InstallerIsEnabled(installer)
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("Enabled is a command that checks for which", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("which which")},
}
result, err := InstallerIsEnabled(installer)
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("Enabled is a command that checks for nonexistent binary", func(t *testing.T) {
installer := &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("which nonexistent-binary-12345")},
}
result, err := InstallerIsEnabled(installer)
assert.NoError(t, err)
assert.False(t, result)
})
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/logger"
"github.com/stretchr/testify/assert"
)
func newTestGroupInstaller(data *appconfig.InstallerData) *GroupInstaller {
@@ -48,3 +49,169 @@ func TestGroupValidation(t *testing.T) {
}
assertValidationError(t, newTestGroupInstaller(nilSteps).Validate(), "steps")
}
func TestGroupGetData(t *testing.T) {
logger.InitLogger(false)
t.Run("returns the installer data", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
}
installer := newTestGroupInstaller(data)
result := installer.GetData()
assert.Equal(t, data, result)
assert.Equal(t, "group-test", *result.Name)
})
}
func TestGroupGetOpts(t *testing.T) {
logger.InitLogger(false)
t.Run("returns empty opts", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
}
installer := newTestGroupInstaller(data)
opts := installer.GetOpts()
assert.NotNil(t, opts)
})
}
func TestGroupGetBinName(t *testing.T) {
logger.InitLogger(false)
t.Run("returns name when bin_name is not set", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("my-group"),
Type: appconfig.InstallerTypeGroup,
}
installer := newTestGroupInstaller(data)
assert.Equal(t, "my-group", installer.GetBinName())
})
t.Run("returns bin_name when set", func(t *testing.T) {
binName := "custom-bin"
data := &appconfig.InstallerData{
Name: strPtr("my-group"),
Type: appconfig.InstallerTypeGroup,
BinName: &binName,
}
installer := newTestGroupInstaller(data)
assert.Equal(t, "custom-bin", installer.GetBinName())
})
t.Run("returns name when bin_name is empty", func(t *testing.T) {
binName := ""
data := &appconfig.InstallerData{
Name: strPtr("my-group"),
Type: appconfig.InstallerTypeGroup,
BinName: &binName,
}
installer := newTestGroupInstaller(data)
assert.Equal(t, "my-group", installer.GetBinName())
})
}
func TestGroupCheckIsInstalled(t *testing.T) {
logger.InitLogger(false)
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "true"
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
CheckInstalled: &checkCmd,
}
installer := newTestGroupInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("runs custom check that fails", func(t *testing.T) {
checkCmd := "false"
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
CheckInstalled: &checkCmd,
}
installer := newTestGroupInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.False(t, result)
})
}
func TestGroupCheckNeedsUpdate(t *testing.T) {
logger.InitLogger(false)
t.Run("returns true when no custom check", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
}
installer := newTestGroupInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "false" // Returns exit code 1, meaning no update
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
CheckHasUpdate: &checkCmd,
}
installer := newTestGroupInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.False(t, result)
})
}
func TestNewGroupInstaller(t *testing.T) {
logger.InitLogger(false)
t.Run("creates installer with config and data", func(t *testing.T) {
cfg := &appconfig.AppConfig{}
data := &appconfig.InstallerData{
Name: strPtr("group-test"),
Type: appconfig.InstallerTypeGroup,
}
installer := NewGroupInstaller(cfg, data)
assert.NotNil(t, installer)
assert.Equal(t, cfg, installer.Config)
assert.Equal(t, data, installer.Data)
})
}
func TestGroupValidationWithMultipleSteps(t *testing.T) {
logger.InitLogger(false)
t.Run("valid with multiple steps", func(t *testing.T) {
step1 := appconfig.InstallerData{
Name: strPtr("step1"),
Type: appconfig.InstallerTypeBrew,
}
step2 := appconfig.InstallerData{
Name: strPtr("step2"),
Type: appconfig.InstallerTypeShell,
}
data := &appconfig.InstallerData{
Name: strPtr("group-multi"),
Type: appconfig.InstallerTypeGroup,
Steps: &[]appconfig.InstallerData{step1, step2},
}
assertNoValidationErrors(t, newTestGroupInstaller(data).Validate())
})
}

View File

@@ -0,0 +1,233 @@
package installer
import (
"testing"
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/logger"
"github.com/chenasraf/sofmani/platform"
"github.com/stretchr/testify/assert"
)
func init() {
logger.InitLogger(false)
}
func TestFillDefaults(t *testing.T) {
t.Run("fills nil fields with empty values", func(t *testing.T) {
data := &appconfig.InstallerData{}
FillDefaults(data)
assert.NotNil(t, data.Env)
assert.NotNil(t, data.Opts)
assert.NotNil(t, data.PlatformEnv)
assert.NotNil(t, data.EnvShell)
assert.NotNil(t, data.Platforms)
assert.NotNil(t, data.Steps)
assert.NotNil(t, data.Tags)
})
t.Run("does not overwrite existing values", func(t *testing.T) {
existingEnv := map[string]string{"KEY": "VALUE"}
existingOpts := map[string]any{"opt": "val"}
data := &appconfig.InstallerData{
Env: &existingEnv,
Opts: &existingOpts,
}
FillDefaults(data)
assert.Equal(t, "VALUE", (*data.Env)["KEY"])
assert.Equal(t, "val", (*data.Opts)["opt"])
})
t.Run("sets Linux-only platforms for apt installer", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeApt,
}
FillDefaults(data)
assert.NotNil(t, data.Platforms.Only)
assert.Contains(t, *data.Platforms.Only, platform.PlatformLinux)
})
t.Run("sets Linux-only platforms for apk installer", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeApk,
}
FillDefaults(data)
assert.NotNil(t, data.Platforms.Only)
assert.Contains(t, *data.Platforms.Only, platform.PlatformLinux)
})
t.Run("sets Linux-only platforms for pacman installer", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypePacman,
}
FillDefaults(data)
assert.NotNil(t, data.Platforms.Only)
assert.Contains(t, *data.Platforms.Only, platform.PlatformLinux)
})
t.Run("sets Linux-only platforms for yay installer", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeYay,
}
FillDefaults(data)
assert.NotNil(t, data.Platforms.Only)
assert.Contains(t, *data.Platforms.Only, platform.PlatformLinux)
})
}
func TestInstallerWithDefaults_Comprehensive(t *testing.T) {
t.Run("applies base defaults when no type defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, nil)
assert.NotNil(t, result.Env)
assert.NotNil(t, result.Opts)
})
t.Run("applies type-specific defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
defaultOpts := map[string]any{"tap": "default/tap"}
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeBrew: {
Opts: &defaultOpts,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.Equal(t, "default/tap", (*result.Opts)["tap"])
})
t.Run("applies env defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
defaultEnv := map[string]string{"DEFAULT_VAR": "default_value"}
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeBrew: {
Env: &defaultEnv,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.Equal(t, "default_value", (*result.Env)["DEFAULT_VAR"])
})
t.Run("applies hook defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
preInstall := "echo pre"
postInstall := "echo post"
preUpdate := "echo pre-update"
postUpdate := "echo post-update"
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeBrew: {
PreInstall: &preInstall,
PostInstall: &postInstall,
PreUpdate: &preUpdate,
PostUpdate: &postUpdate,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.Equal(t, "echo pre", *result.PreInstall)
assert.Equal(t, "echo post", *result.PostInstall)
assert.Equal(t, "echo pre-update", *result.PreUpdate)
assert.Equal(t, "echo post-update", *result.PostUpdate)
})
t.Run("applies check command defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
checkInstalled := "which myapp"
checkHasUpdate := "myapp --version"
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeBrew: {
CheckInstalled: &checkInstalled,
CheckHasUpdate: &checkHasUpdate,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.Equal(t, "which myapp", *result.CheckInstalled)
assert.Equal(t, "myapp --version", *result.CheckHasUpdate)
})
t.Run("applies platform defaults", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
platforms := platform.Platforms{
Only: &[]platform.Platform{platform.PlatformMacos},
}
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeBrew: {
Platforms: &platforms,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.NotNil(t, result.Platforms.Only)
assert.Contains(t, *result.Platforms.Only, platform.PlatformMacos)
})
t.Run("does not apply defaults for different installer type", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
defaultOpts := map[string]any{"npm_option": "value"}
defaults := &appconfig.AppConfigDefaults{
Type: &map[appconfig.InstallerType]appconfig.InstallerData{
appconfig.InstallerTypeNpm: {
Opts: &defaultOpts,
},
},
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.NotContains(t, *result.Opts, "npm_option")
})
t.Run("handles nil defaults gracefully", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, nil)
assert.NotNil(t, result)
assert.NotNil(t, result.Opts)
})
t.Run("handles empty defaults type map", func(t *testing.T) {
data := &appconfig.InstallerData{
Type: appconfig.InstallerTypeBrew,
}
defaults := &appconfig.AppConfigDefaults{
Type: nil,
}
result := InstallerWithDefaults(data, appconfig.InstallerTypeBrew, defaults)
assert.NotNil(t, result)
})
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/logger"
"github.com/stretchr/testify/assert"
)
func newTestManifestInstaller(data *appconfig.InstallerData) *ManifestInstaller {
@@ -63,4 +64,181 @@ func TestManifestValidation(t *testing.T) {
},
}
assertValidationError(t, newTestManifestInstaller(emptyRef).Validate(), "ref")
// 🔴 Nil opts
nilOpts := &appconfig.InstallerData{
Name: strPtr("manifest-nil-opts"),
Type: appconfig.InstallerTypeManifest,
Opts: nil,
}
assertValidationError(t, newTestManifestInstaller(nilOpts).Validate(), "source")
}
func TestManifestGetOpts(t *testing.T) {
logger.InitLogger(false)
t.Run("returns all opts when set", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
Opts: &map[string]any{
"source": "https://github.com/user/repo.git",
"path": "manifest.yml",
"ref": "develop",
},
}
installer := newTestManifestInstaller(data)
opts := installer.GetOpts()
assert.NotNil(t, opts.Source)
assert.Equal(t, "https://github.com/user/repo.git", *opts.Source)
assert.NotNil(t, opts.Path)
assert.Equal(t, "manifest.yml", *opts.Path)
assert.NotNil(t, opts.Ref)
assert.Equal(t, "develop", *opts.Ref)
})
t.Run("returns nil fields when opts is nil", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
Opts: nil,
}
installer := newTestManifestInstaller(data)
opts := installer.GetOpts()
assert.Nil(t, opts.Source)
assert.Nil(t, opts.Path)
assert.Nil(t, opts.Ref)
})
t.Run("handles partial opts", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
Opts: &map[string]any{
"source": "https://github.com/user/repo.git",
},
}
installer := newTestManifestInstaller(data)
opts := installer.GetOpts()
assert.NotNil(t, opts.Source)
assert.Equal(t, "https://github.com/user/repo.git", *opts.Source)
assert.Nil(t, opts.Path)
assert.Nil(t, opts.Ref)
})
t.Run("handles wrong type values gracefully", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
Opts: &map[string]any{
"source": 123, // Wrong type
"path": true, // Wrong type
"ref": []int{}, // Wrong type
},
}
installer := newTestManifestInstaller(data)
opts := installer.GetOpts()
// Should return nil when type assertion fails
assert.Nil(t, opts.Source)
assert.Nil(t, opts.Path)
assert.Nil(t, opts.Ref)
})
}
func TestManifestGetData(t *testing.T) {
logger.InitLogger(false)
t.Run("returns the installer data", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
}
installer := newTestManifestInstaller(data)
result := installer.GetData()
assert.Equal(t, data, result)
assert.Equal(t, "manifest-test", *result.Name)
})
}
func TestManifestCheckIsInstalled(t *testing.T) {
logger.InitLogger(false)
t.Run("returns false when no custom check", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
}
installer := newTestManifestInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.False(t, result)
})
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "true"
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
CheckInstalled: &checkCmd,
}
installer := newTestManifestInstaller(data)
result, err := installer.CheckIsInstalled()
assert.NoError(t, err)
assert.True(t, result)
})
}
func TestManifestCheckNeedsUpdate(t *testing.T) {
logger.InitLogger(false)
t.Run("returns true when no custom check", func(t *testing.T) {
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
}
installer := newTestManifestInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.True(t, result)
})
t.Run("runs custom check when provided", func(t *testing.T) {
checkCmd := "false" // Returns exit code 1, meaning no update
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
CheckHasUpdate: &checkCmd,
}
installer := newTestManifestInstaller(data)
result, err := installer.CheckNeedsUpdate()
assert.NoError(t, err)
assert.False(t, result)
})
}
func TestNewManifestInstaller(t *testing.T) {
logger.InitLogger(false)
t.Run("creates installer with config and data", func(t *testing.T) {
cfg := &appconfig.AppConfig{}
data := &appconfig.InstallerData{
Name: strPtr("manifest-test"),
Type: appconfig.InstallerTypeManifest,
}
installer := NewManifestInstaller(cfg, data)
assert.NotNil(t, installer)
assert.Equal(t, cfg, installer.Config)
assert.Equal(t, data, installer.Info)
assert.Equal(t, data, installer.Data)
})
}

View File

@@ -1,6 +1,7 @@
package platform
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
@@ -28,3 +29,342 @@ func TestGetShouldRunOnOSExcept(t *testing.T) {
assert.False(t, platforms.GetShouldRunOnOS(PlatformMacos))
assert.True(t, platforms.GetShouldRunOnOS(PlatformLinux))
}
func TestGetShouldRunOnOSEdgeCases(t *testing.T) {
t.Run("nil Platforms returns true", func(t *testing.T) {
var platforms *Platforms
assert.True(t, platforms.GetShouldRunOnOS(PlatformMacos))
})
t.Run("empty Platforms returns true for any OS", func(t *testing.T) {
platforms := Platforms{}
assert.True(t, platforms.GetShouldRunOnOS(PlatformMacos))
assert.True(t, platforms.GetShouldRunOnOS(PlatformLinux))
assert.True(t, platforms.GetShouldRunOnOS(PlatformWindows))
})
t.Run("Only takes precedence over Except", func(t *testing.T) {
// If both Only and Except are set, Only should take precedence
platforms := Platforms{
Only: &[]Platform{PlatformMacos},
Except: &[]Platform{PlatformMacos},
}
// Only should be checked first
assert.True(t, platforms.GetShouldRunOnOS(PlatformMacos))
assert.False(t, platforms.GetShouldRunOnOS(PlatformLinux))
})
t.Run("Multiple platforms in Only", func(t *testing.T) {
platforms := Platforms{Only: &[]Platform{PlatformMacos, PlatformLinux}}
assert.True(t, platforms.GetShouldRunOnOS(PlatformMacos))
assert.True(t, platforms.GetShouldRunOnOS(PlatformLinux))
assert.False(t, platforms.GetShouldRunOnOS(PlatformWindows))
})
t.Run("Multiple platforms in Except", func(t *testing.T) {
platforms := Platforms{Except: &[]Platform{PlatformMacos, PlatformLinux}}
assert.False(t, platforms.GetShouldRunOnOS(PlatformMacos))
assert.False(t, platforms.GetShouldRunOnOS(PlatformLinux))
assert.True(t, platforms.GetShouldRunOnOS(PlatformWindows))
})
}
func TestGetArch(t *testing.T) {
originalArch := archValue
defer func() { SetArch(originalArch) }()
t.Run("returns amd64 for amd64", func(t *testing.T) {
SetArch("amd64")
assert.Equal(t, ArchAmd64, GetArch())
})
t.Run("returns amd64 for x86_64", func(t *testing.T) {
SetArch("x86_64")
assert.Equal(t, ArchAmd64, GetArch())
})
t.Run("returns arm64 for arm64", func(t *testing.T) {
SetArch("arm64")
assert.Equal(t, ArchArm64, GetArch())
})
t.Run("returns arm64 for aarch64", func(t *testing.T) {
SetArch("aarch64")
assert.Equal(t, ArchArm64, GetArch())
})
t.Run("returns unknown arch as-is", func(t *testing.T) {
SetArch("riscv64")
assert.Equal(t, Architecture("riscv64"), GetArch())
})
}
func TestGetArchAlias(t *testing.T) {
originalArch := archValue
defer func() { SetArch(originalArch) }()
t.Run("returns x86_64 for amd64", func(t *testing.T) {
SetArch("amd64")
assert.Equal(t, "x86_64", GetArchAlias())
})
t.Run("returns arm64 for arm64", func(t *testing.T) {
SetArch("arm64")
assert.Equal(t, "arm64", GetArchAlias())
})
t.Run("returns unknown arch as-is", func(t *testing.T) {
SetArch("riscv64")
assert.Equal(t, "riscv64", GetArchAlias())
})
}
func TestGetArchGnu(t *testing.T) {
originalArch := archValue
defer func() { SetArch(originalArch) }()
t.Run("returns x86_64 for amd64", func(t *testing.T) {
SetArch("amd64")
assert.Equal(t, "x86_64", GetArchGnu())
})
t.Run("returns aarch64 for arm64", func(t *testing.T) {
SetArch("arm64")
assert.Equal(t, "aarch64", GetArchGnu())
})
t.Run("returns unknown arch as-is", func(t *testing.T) {
SetArch("riscv64")
assert.Equal(t, "riscv64", GetArchGnu())
})
}
func TestPlatformMapResolve(t *testing.T) {
originalOS := osValue
defer func() { SetOS(originalOS) }()
t.Run("returns nil for nil PlatformMap", func(t *testing.T) {
var pm *PlatformMap[string]
assert.Nil(t, pm.Resolve())
})
t.Run("returns MacOS value on darwin", func(t *testing.T) {
SetOS("darwin")
value := "mac-value"
pm := &PlatformMap[string]{MacOS: &value}
assert.Equal(t, &value, pm.Resolve())
})
t.Run("returns Linux value on linux", func(t *testing.T) {
SetOS("linux")
value := "linux-value"
pm := &PlatformMap[string]{Linux: &value}
assert.Equal(t, &value, pm.Resolve())
})
t.Run("returns Windows value on windows", func(t *testing.T) {
SetOS("windows")
value := "windows-value"
pm := &PlatformMap[string]{Windows: &value}
assert.Equal(t, &value, pm.Resolve())
})
t.Run("returns nil when platform value not set", func(t *testing.T) {
SetOS("darwin")
pm := &PlatformMap[string]{Linux: strPtr("linux-only")}
assert.Nil(t, pm.Resolve())
})
t.Run("returns nil for unknown OS", func(t *testing.T) {
SetOS("freebsd")
pm := &PlatformMap[string]{
MacOS: strPtr("mac"),
Linux: strPtr("linux"),
Windows: strPtr("windows"),
}
assert.Nil(t, pm.Resolve())
})
}
func TestPlatformMapResolveWithFallback(t *testing.T) {
originalOS := osValue
defer func() { SetOS(originalOS) }()
t.Run("returns primary value when set", func(t *testing.T) {
SetOS("darwin")
primary := &PlatformMap[string]{MacOS: strPtr("primary")}
fallback := PlatformMap[string]{MacOS: strPtr("fallback")}
assert.Equal(t, "primary", primary.ResolveWithFallback(fallback))
})
t.Run("returns fallback value when primary not set", func(t *testing.T) {
SetOS("darwin")
primary := &PlatformMap[string]{Linux: strPtr("linux-only")}
fallback := PlatformMap[string]{MacOS: strPtr("fallback")}
assert.Equal(t, "fallback", primary.ResolveWithFallback(fallback))
})
}
func TestParsePlatformSingleValue(t *testing.T) {
t.Run("sets value for all platforms", func(t *testing.T) {
pm := ParsePlatformSingleValue("test-value")
assert.NotNil(t, pm)
assert.Equal(t, "test-value", *pm.MacOS)
assert.Equal(t, "test-value", *pm.Linux)
assert.Equal(t, "test-value", *pm.Windows)
})
}
func TestParselatformMap(t *testing.T) {
t.Run("parses macos value", func(t *testing.T) {
values := map[string]string{"macos": "mac-value"}
pm := ParselatformMap(values)
assert.Equal(t, "mac-value", *pm.MacOS)
assert.Nil(t, pm.Linux)
assert.Nil(t, pm.Windows)
})
t.Run("parses linux value", func(t *testing.T) {
values := map[string]string{"linux": "linux-value"}
pm := ParselatformMap(values)
assert.Nil(t, pm.MacOS)
assert.Equal(t, "linux-value", *pm.Linux)
assert.Nil(t, pm.Windows)
})
t.Run("parses windows value", func(t *testing.T) {
values := map[string]string{"windows": "windows-value"}
pm := ParselatformMap(values)
assert.Nil(t, pm.MacOS)
assert.Nil(t, pm.Linux)
assert.Equal(t, "windows-value", *pm.Windows)
})
t.Run("parses all platforms", func(t *testing.T) {
values := map[string]string{
"macos": "mac",
"linux": "lin",
"windows": "win",
}
pm := ParselatformMap(values)
assert.Equal(t, "mac", *pm.MacOS)
assert.Equal(t, "lin", *pm.Linux)
assert.Equal(t, "win", *pm.Windows)
})
}
func TestNewPlatformMap(t *testing.T) {
t.Run("returns nil for nil input", func(t *testing.T) {
pm := NewPlatformMap[string](nil)
assert.Nil(t, pm)
})
t.Run("handles single value", func(t *testing.T) {
pm := NewPlatformMap[string]("single-value")
assert.Equal(t, "single-value", *pm.MacOS)
assert.Equal(t, "single-value", *pm.Linux)
assert.Equal(t, "single-value", *pm.Windows)
})
t.Run("handles map of values", func(t *testing.T) {
input := map[string]string{"macos": "mac", "linux": "lin"}
pm := NewPlatformMap[string](input)
assert.Equal(t, "mac", *pm.MacOS)
assert.Equal(t, "lin", *pm.Linux)
assert.Nil(t, pm.Windows)
})
t.Run("handles pointer to value", func(t *testing.T) {
value := "ptr-value"
pm := NewPlatformMap[string](&value)
assert.Equal(t, "ptr-value", *pm.MacOS)
assert.Equal(t, "ptr-value", *pm.Linux)
assert.Equal(t, "ptr-value", *pm.Windows)
})
t.Run("handles nil pointer", func(t *testing.T) {
var ptr *string
pm := NewPlatformMap[string](ptr)
assert.Nil(t, pm)
})
t.Run("handles map of pointers", func(t *testing.T) {
mac := "mac"
input := map[string]*string{"macos": &mac, "linux": nil}
pm := NewPlatformMap[string](input)
assert.Equal(t, "mac", *pm.MacOS)
assert.Nil(t, pm.Linux)
})
}
func TestSetOSAndSetArch(t *testing.T) {
t.Run("SetOS changes platform detection", func(t *testing.T) {
originalOS := osValue
defer func() { SetOS(originalOS) }()
SetOS("darwin")
assert.Equal(t, PlatformMacos, GetPlatform())
SetOS("linux")
assert.Equal(t, PlatformLinux, GetPlatform())
SetOS("windows")
assert.Equal(t, PlatformWindows, GetPlatform())
})
t.Run("SetArch changes architecture detection", func(t *testing.T) {
originalArch := archValue
defer func() { SetArch(originalArch) }()
SetArch("amd64")
assert.Equal(t, ArchAmd64, GetArch())
SetArch("arm64")
assert.Equal(t, ArchArm64, GetArch())
})
}
func TestDockerOSMap(t *testing.T) {
t.Run("has linux for MacOS", func(t *testing.T) {
assert.Equal(t, "linux", *DockerOSMap.MacOS)
})
t.Run("has linux for Linux", func(t *testing.T) {
assert.Equal(t, "linux", *DockerOSMap.Linux)
})
t.Run("has windows for Windows", func(t *testing.T) {
assert.Equal(t, "windows", *DockerOSMap.Windows)
})
}
func TestPlatformConstants(t *testing.T) {
assert.Equal(t, Platform("macos"), PlatformMacos)
assert.Equal(t, Platform("linux"), PlatformLinux)
assert.Equal(t, Platform("windows"), PlatformWindows)
}
func TestArchConstants(t *testing.T) {
assert.Equal(t, Architecture("amd64"), ArchAmd64)
assert.Equal(t, Architecture("arm64"), ArchArm64)
}
func TestGetOSReturnsRuntimeValue(t *testing.T) {
// Temporarily reset osValue to empty to test fallback
originalOS := osValue
defer func() { SetOS(originalOS) }()
osValue = ""
result := getOS()
assert.Equal(t, runtime.GOOS, result)
}
func TestGetArchReturnsRuntimeValue(t *testing.T) {
// Temporarily reset archValue to empty to test fallback
originalArch := archValue
defer func() { SetArch(originalArch) }()
archValue = ""
result := getArch()
assert.Equal(t, runtime.GOARCH, result)
}

55
utils/cache_test.go Normal file
View File

@@ -0,0 +1,55 @@
package utils
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetCacheDir(t *testing.T) {
t.Run("returns a valid path", func(t *testing.T) {
cacheDir, err := GetCacheDir()
assert.NoError(t, err)
assert.NotEmpty(t, cacheDir)
})
t.Run("path ends with .cache", func(t *testing.T) {
cacheDir, err := GetCacheDir()
assert.NoError(t, err)
assert.True(t, strings.HasSuffix(cacheDir, ".cache"))
})
t.Run("creates the directory if it does not exist", func(t *testing.T) {
cacheDir, err := GetCacheDir()
assert.NoError(t, err)
// Check directory exists
info, err := os.Stat(cacheDir)
assert.NoError(t, err)
assert.True(t, info.IsDir())
})
t.Run("directory is in user config directory", func(t *testing.T) {
cacheDir, err := GetCacheDir()
assert.NoError(t, err)
confDir, err := os.UserConfigDir()
assert.NoError(t, err)
expectedPath := filepath.Join(confDir, ".cache")
assert.Equal(t, expectedPath, cacheDir)
})
t.Run("returns same path on subsequent calls", func(t *testing.T) {
cacheDir1, err := GetCacheDir()
assert.NoError(t, err)
cacheDir2, err := GetCacheDir()
assert.NoError(t, err)
assert.Equal(t, cacheDir1, cacheDir2)
})
}

200
utils/command_test.go Normal file
View File

@@ -0,0 +1,200 @@
package utils
import (
"testing"
"github.com/chenasraf/sofmani/logger"
"github.com/chenasraf/sofmani/platform"
"github.com/stretchr/testify/assert"
)
func init() {
logger.InitLogger(false)
}
func TestRunCmdGetSuccess(t *testing.T) {
tests := []struct {
name string
bin string
args []string
expectedResult bool
}{
{
name: "successful command",
bin: "echo",
args: []string{"hello"},
expectedResult: true,
},
{
name: "failing command",
bin: "false",
args: []string{},
expectedResult: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := RunCmdGetSuccess(nil, tt.bin, tt.args...)
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
})
}
}
func TestRunCmdGetOutput(t *testing.T) {
tests := []struct {
name string
bin string
args []string
expectedOutput string
expectError bool
}{
{
name: "echo command",
bin: "echo",
args: []string{"hello"},
expectedOutput: "hello\n",
expectError: false,
},
{
name: "printf command",
bin: "printf",
args: []string{"test"},
expectedOutput: "test",
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := RunCmdGetOutput(nil, tt.bin, tt.args...)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedOutput, string(output))
}
})
}
}
func TestRunCmdGetOutputWithEnv(t *testing.T) {
env := []string{"TEST_VAR=hello_world"}
output, err := RunCmdGetOutput(env, "sh", "-c", "echo $TEST_VAR")
assert.NoError(t, err)
assert.Equal(t, "hello_world\n", string(output))
}
func TestRunCmdPassThroughChained(t *testing.T) {
// Test successful chain
commands := [][]string{
{"true"},
{"true"},
}
err := RunCmdPassThroughChained(nil, commands)
assert.NoError(t, err)
// Test chain with failure in middle
commandsWithFailure := [][]string{
{"true"},
{"false"},
{"true"},
}
err = RunCmdPassThroughChained(nil, commandsWithFailure)
assert.Error(t, err)
}
func TestGetShellWhich(t *testing.T) {
result := GetShellWhich()
curPlatform := platform.GetPlatform()
switch curPlatform {
case platform.PlatformWindows:
assert.Equal(t, "where", result)
case platform.PlatformLinux, platform.PlatformMacos:
assert.Equal(t, "which", result)
}
}
func TestGetOSShell(t *testing.T) {
curPlatform := platform.GetPlatform()
// Test with nil envShell
result := GetOSShell(nil)
switch curPlatform {
case platform.PlatformWindows:
assert.Equal(t, "cmd", result)
case platform.PlatformLinux, platform.PlatformMacos:
// Should return SHELL env var or default to bash
assert.NotEmpty(t, result)
}
// Test with custom envShell override
if curPlatform != platform.PlatformWindows {
customShell := "zsh"
envShell := &platform.PlatformMap[string]{
MacOS: &customShell,
Linux: &customShell,
}
result = GetOSShell(envShell)
assert.Equal(t, "zsh", result)
}
}
func TestGetOSShellArgs(t *testing.T) {
curPlatform := platform.GetPlatform()
args := GetOSShellArgs("echo hello")
switch curPlatform {
case platform.PlatformWindows:
assert.Equal(t, []string{"/C", "echo hello & exit %ERRORLEVEL%"}, args)
case platform.PlatformLinux, platform.PlatformMacos:
assert.Equal(t, []string{"-c", "echo hello; exit $?"}, args)
}
}
func TestGetShellScript(t *testing.T) {
curPlatform := platform.GetPlatform()
result := getShellScript("/tmp")
switch curPlatform {
case platform.PlatformWindows:
assert.Equal(t, "/tmp/install.bat", result)
case platform.PlatformLinux, platform.PlatformMacos:
assert.Equal(t, "/tmp/install", result)
}
}
func TestGetScriptContents(t *testing.T) {
curPlatform := platform.GetPlatform()
if curPlatform == platform.PlatformWindows {
content, err := getScriptContents("echo hello", nil)
assert.NoError(t, err)
assert.Contains(t, content, "@echo off")
assert.Contains(t, content, "echo hello")
assert.Contains(t, content, "exit /b %ERRORLEVEL%")
} else {
content, err := getScriptContents("echo hello", nil)
assert.NoError(t, err)
assert.Contains(t, content, "#!/usr/bin/env")
assert.Contains(t, content, "echo hello")
assert.Contains(t, content, "exit $?")
}
}
func TestRunCmdAsFile(t *testing.T) {
curPlatform := platform.GetPlatform()
if curPlatform != platform.PlatformWindows {
// Test simple script execution
err := RunCmdAsFile(nil, "exit 0", nil)
assert.NoError(t, err)
// Test script with failure
err = RunCmdAsFile(nil, "exit 1", nil)
assert.Error(t, err)
}
}