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
23 changes: 14 additions & 9 deletions shared/credstore/credstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (

"gopkg.in/yaml.v3"

"github.com/open-cli-collective/cli-common/statedir"

"github.com/open-cli-collective/atlassian-go/auth"
)

Expand Down Expand Up @@ -69,17 +71,20 @@ type Store struct {
JTK ToolSection `yaml:"jtk,omitempty"`
}

// DefaultPath returns the canonical shared store path, honoring
// $XDG_CONFIG_HOME if set.
func DefaultPath() string {
if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
return filepath.Join(xdg, "atlassian-cli", "config.yml")
}
home, err := os.UserHomeDir()
// DefaultPath returns the canonical shared store path. It resolves via
// the shared statedir resolver (os.UserConfigDir()/atlassian-cli),
// which honors $XDG_CONFIG_HOME on Linux and returns the OS-native
// config dir on macOS/Windows. A relative or unresolvable
// $XDG_CONFIG_HOME now returns an error (the §1.1 intentional
// tightening) instead of the prior silent cwd-relative
// `./.atlassian-cli` fallback. Existence-check callers treat the error
// as "no shared file".
func DefaultPath() (string, error) {
dir, err := statedir.Scope{Name: "atlassian-cli"}.ConfigDir()
if err != nil {
return filepath.Join(".", ".atlassian-cli", "config.yml")
return "", err
}
return filepath.Join(home, ".config", "atlassian-cli", "config.yml")
return filepath.Join(dir, "config.yml"), nil
}

// Load reads the store at path. An absent file returns an empty Store
Expand Down
39 changes: 35 additions & 4 deletions shared/credstore/credstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"strings"
"testing"

"github.com/open-cli-collective/cli-common/statedir"
"github.com/open-cli-collective/cli-common/statedirtest"

"github.com/open-cli-collective/atlassian-go/auth"
"github.com/open-cli-collective/atlassian-go/testutil"
)
Expand Down Expand Up @@ -323,8 +326,36 @@ func TestURLForCFL(t *testing.T) {
}
}

func TestDefaultPath_HonorsXDG(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", "/custom/xdg")
got := DefaultPath()
testutil.Equal(t, "/custom/xdg/atlassian-cli/config.yml", got)
func TestDefaultPath_DelegatesToStatedir(t *testing.T) {
root := statedirtest.Hermetic(t)

got, err := DefaultPath()
if err != nil {
t.Fatalf("DefaultPath: unexpected error: %v", err)
}
dir, derr := statedir.Scope{Name: "atlassian-cli"}.ConfigDir()
if derr != nil {
t.Fatalf("statedir ConfigDir: %v", derr)
}
testutil.Equal(t, filepath.Join(dir, "config.yml"), got)
if !strings.HasPrefix(got, root) {
t.Fatalf("DefaultPath %q escaped hermetic root %q (real-dir leak)", got, root)
}
}

func TestDefaultPath_RelativeXDGErrorParity(t *testing.T) {
// statedir rejects a relative $XDG_CONFIG_HOME (the §1.1 tightening)
// instead of the prior silent ./.atlassian-cli fallback. The exact
// rejection semantics are owned by cli-common's resolver tests; here
// we only assert DefaultPath faithfully propagates whatever the
// resolver decides on this platform (delegation parity, not a
// hard-coded OS branch).
statedirtest.Hermetic(t)
t.Setenv("XDG_CONFIG_HOME", "relative/not/absolute")

_, resolverErr := statedir.Scope{Name: "atlassian-cli"}.ConfigDir()
_, gotErr := DefaultPath()
if (gotErr == nil) != (resolverErr == nil) {
t.Fatalf("DefaultPath error parity mismatch: DefaultPath=%v resolver=%v", gotErr, resolverErr)
}
}
Loading
Loading