Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func TestApplyUserDefaults(t *testing.T) {
t.Setenv("HOME", tmpDir) // For non-Unix systems.

if !reflect.DeepEqual(tt.user, deployer.Config{}) {
configPath, err := paths.UserConfigPath()
configPath, err := paths.UserConfigPath(false)
require.NoError(t, err)
require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
data, err := yaml.Marshal(tt.user)
Expand All @@ -415,7 +415,7 @@ func TestApplyUserDefaults(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", tmpDir)
t.Setenv("HOME", tmpDir) // For non-Unix systems.

configPath, err := paths.UserConfigPath()
configPath, err := paths.UserConfigPath(false)
require.NoError(t, err)
require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
require.NoError(t, os.WriteFile(configPath, []byte(`invalid: [yaml`), 0o644))
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func main() {
// current config. This essentially means, that the user config can
// override values, which are already initialized in NewConfig().
func tryApplyUserDefaults(log *logger.Logger, config *deployer.Config) error {
path, err := paths.UserConfigPath()
path, err := paths.UserConfigPath(true)
if err != nil {
return err
}
Expand Down
28 changes: 26 additions & 2 deletions internal/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,31 @@ import (

const appName = "roxie"

func UserConfigPath() (string, error) {
const envUserConfigPath = "ROXIE_USER_CONFIG_PATH"

func UserConfigPath(validateUserConfig bool) (string, error) {
if overwrittenPath := os.Getenv(envUserConfigPath); overwrittenPath != "" {
if validateUserConfig {
if err := fileIsReadable(overwrittenPath); err != nil {
return "", fmt.Errorf("checking if user config file %q from environment variable %s is readable: %w",
overwrittenPath, envUserConfigPath, err)
}
}
return overwrittenPath, nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return getDefaultUserConfigPath()
}

func fileIsReadable(path string) error {
fd, err := os.Open(path)
if err != nil {
return err
}
_ = fd.Close()
return nil
}

func getDefaultUserConfigPath() (string, error) {
dir, err := configDir()
if err != nil {
return "", fmt.Errorf("retrieving user config path: %w", err)
Expand All @@ -17,7 +41,7 @@ func UserConfigPath() (string, error) {
}

func UserConfigPathString() string {
path, err := UserConfigPath()
path, err := UserConfigPath(false)
if err != nil {
return "(UNAVAILABLE)"
}
Expand Down
147 changes: 147 additions & 0 deletions internal/paths/paths_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package paths

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
defaultUserConfigPath = func() string {
// Ok to panic here. The tests would fail one way or another anyway.
path, err := getDefaultUserConfigPath()
if err != nil {
panic(fmt.Sprintf("failed to obtain default user config path: %v", err))
}
return path
}()
)

func TestUserConfigPath(t *testing.T) {
existentUserConfig := createTempFile(t, "cfg.yaml")
tests := []struct {
name string
envValue string
setEnv bool
expected string
validateUserConfig bool
expectErr bool
}{
{
name: "returns env var value when set and file exists with validation",
envValue: existentUserConfig,
setEnv: true,
expected: existentUserConfig,
validateUserConfig: true,
},
{
name: "validation returns error when env var value set to non-existent file",
envValue: "/non-existent/roxie.yaml",
setEnv: true,
validateUserConfig: true,
expectErr: true,
},
{
name: "returns env var value when set to non-existent file with validation off",
envValue: "/non-existent/roxie.yaml",
setEnv: true,
expected: "/non-existent/roxie.yaml",
validateUserConfig: false,
},
{
name: "returns default path when env var is not set with validation",
setEnv: false,
expected: defaultUserConfigPath,
validateUserConfig: true,
},
{
name: "returns default path when env var is set but empty",
envValue: "",
setEnv: true,
expected: defaultUserConfigPath,
validateUserConfig: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanEnvironment(t)

if tt.setEnv {
t.Setenv(envUserConfigPath, tt.envValue)
}
got, err := UserConfigPath(tt.validateUserConfig)
if tt.expectErr {
assert.Error(t, err, "expected UserConfigPath() to return error")
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, got)
})
}
}

func TestUserConfigPathString(t *testing.T) {
existentUserConfig := createTempFile(t, "cfg.yaml")
tests := []struct {
name string
envValue string
setEnv bool
expected string
}{
{
name: "returns env var value when set and file does not exist",
envValue: "/nonexistent/path.yaml",
setEnv: true,
expected: "/nonexistent/path.yaml",
},
{
name: "returns env var value when set and file exists",
envValue: existentUserConfig,
setEnv: true,
expected: existentUserConfig,
},
{
name: "returns default path when env var is not set",
setEnv: false,
expected: defaultUserConfigPath,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{
name: "returns default path when env var set but empty",
envValue: "",
setEnv: true,
expected: defaultUserConfigPath,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanEnvironment(t)

if tt.setEnv {
t.Setenv(envUserConfigPath, tt.envValue)
}
assert.Equal(t, tt.expected, UserConfigPathString())
})
}
}

func createTempFile(t *testing.T, name string) string {
tmpdir := t.TempDir()
path := tmpdir + "/" + name
err := os.WriteFile(path, nil, 0o600)
require.NoErrorf(t, err, "failed to create temporary file %q", path)
return path
}

func cleanEnvironment(t *testing.T) {
// Clean environment for the duration of this test case.
// The os.Unsetenv() in combination with the cleanup handler installed by t.Setenv()
// guarantees that for the duration of the test, there is *no* variable set in the
// environment at all, not even an empty one.
t.Setenv(envUserConfigPath, "")
os.Unsetenv(envUserConfigPath)
}
Loading